Sync repo files to b22
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Possible types to restore the "allow flight" property
|
||||
* from LimboPlayer to Bukkit Player.
|
||||
*/
|
||||
public enum AllowFlightRestoreType {
|
||||
|
||||
/** Set value from LimboPlayer to Player. */
|
||||
RESTORE {
|
||||
@Override
|
||||
public void restoreAllowFlight(Player player, LimboPlayer limbo) {
|
||||
player.setAllowFlight(limbo.isCanFly());
|
||||
}
|
||||
},
|
||||
|
||||
/** Always set flight enabled to true. */
|
||||
ENABLE {
|
||||
@Override
|
||||
public void restoreAllowFlight(Player player, LimboPlayer limbo) {
|
||||
player.setAllowFlight(true);
|
||||
}
|
||||
},
|
||||
|
||||
/** Always set flight enabled to false. */
|
||||
DISABLE {
|
||||
@Override
|
||||
public void restoreAllowFlight(Player player, LimboPlayer limbo) {
|
||||
player.setAllowFlight(false);
|
||||
}
|
||||
},
|
||||
|
||||
/** The user's flight handling is not modified. */
|
||||
NOTHING {
|
||||
@Override
|
||||
public void restoreAllowFlight(Player player, LimboPlayer limbo) {
|
||||
// noop
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processPlayer(Player player) {
|
||||
// noop
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores the "allow flight" property from the LimboPlayer to the Player.
|
||||
* This method behaves differently for each restoration type.
|
||||
*
|
||||
* @param player the player to modify
|
||||
* @param limbo the limbo player to read from
|
||||
*/
|
||||
public abstract void restoreAllowFlight(Player player, LimboPlayer limbo);
|
||||
|
||||
/**
|
||||
* Processes the player when a LimboPlayer instance is created based on him. Typically this
|
||||
* method revokes the {@code allowFlight} property to be restored again later.
|
||||
*
|
||||
* @param player the player to process
|
||||
*/
|
||||
public void processPlayer(Player player) {
|
||||
player.setAllowFlight(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.initialization.Reloadable;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
import fr.xephi.authme.permission.PermissionsManager;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.PluginSettings;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Changes the permission group according to the auth status of the player and the configuration.
|
||||
* <p>
|
||||
* If this feature is enabled, the <i>primary permissions group</i> of a player is replaced until he has
|
||||
* logged in. Some permission plugins have a notion of a primary group; for other permission plugins the
|
||||
* first group is simply taken.
|
||||
* <p>
|
||||
* The groups that are used as replacement until the player logs in is configurable and depends on if
|
||||
* the player is registered or not. Note that some (all?) permission systems require the group to actually
|
||||
* exist for the replacement to take place. Furthermore, since some permission groups require that players
|
||||
* be in at least one group, this will mean that the player is not removed from his primary group.
|
||||
*/
|
||||
class AuthGroupHandler implements Reloadable {
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(AuthGroupHandler.class);
|
||||
|
||||
@Inject
|
||||
private PermissionsManager permissionsManager;
|
||||
|
||||
@Inject
|
||||
private Settings settings;
|
||||
|
||||
private UserGroup unregisteredGroup;
|
||||
private UserGroup registeredGroup;
|
||||
|
||||
AuthGroupHandler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the group of a player by its authentication status.
|
||||
*
|
||||
* @param player the player
|
||||
* @param limbo the associated limbo player (nullable)
|
||||
* @param groupType the group type
|
||||
*/
|
||||
void setGroup(Player player, LimboPlayer limbo, AuthGroupType groupType) {
|
||||
if (!useAuthGroups()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<UserGroup> previousGroups = limbo == null ? Collections.emptyList() : limbo.getGroups();
|
||||
|
||||
switch (groupType) {
|
||||
// Implementation note: some permission systems don't support players not being in any group,
|
||||
// so add the new group before removing the old ones
|
||||
case UNREGISTERED:
|
||||
permissionsManager.addGroup(player, unregisteredGroup);
|
||||
permissionsManager.removeGroup(player, registeredGroup);
|
||||
permissionsManager.removeGroups(player, previousGroups);
|
||||
break;
|
||||
|
||||
case REGISTERED_UNAUTHENTICATED:
|
||||
permissionsManager.addGroup(player, registeredGroup);
|
||||
permissionsManager.removeGroup(player, unregisteredGroup);
|
||||
permissionsManager.removeGroups(player, previousGroups);
|
||||
|
||||
break;
|
||||
|
||||
case LOGGED_IN:
|
||||
permissionsManager.addGroups(player, previousGroups);
|
||||
permissionsManager.removeGroup(player, unregisteredGroup);
|
||||
permissionsManager.removeGroup(player, registeredGroup);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("Encountered unhandled auth group type '" + groupType + "'");
|
||||
}
|
||||
|
||||
logger.debug(() -> player.getName() + " changed to "
|
||||
+ groupType + ": has groups " + permissionsManager.getGroups(player));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the auth permissions group function should be used.
|
||||
*
|
||||
* @return true if should be used, false otherwise
|
||||
*/
|
||||
private boolean useAuthGroups() {
|
||||
// Check whether the permissions check is enabled
|
||||
if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure group support is available
|
||||
if (!permissionsManager.hasGroupSupport()) {
|
||||
logger.warning("The current permissions system doesn't have group support, unable to set group!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void reload() {
|
||||
unregisteredGroup = new UserGroup(settings.getProperty(PluginSettings.UNREGISTERED_GROUP));
|
||||
registeredGroup = new UserGroup(settings.getProperty(PluginSettings.REGISTERED_GROUP));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
/**
|
||||
* Represents the group type based on the user's auth status.
|
||||
*/
|
||||
enum AuthGroupType {
|
||||
|
||||
/** Player does not have an account. */
|
||||
UNREGISTERED,
|
||||
|
||||
/** Player is registered but not logged in. */
|
||||
REGISTERED_UNAUTHENTICATED,
|
||||
|
||||
/** Player is logged in. */
|
||||
LOGGED_IN
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
public enum LimboMessageType {
|
||||
|
||||
REGISTER,
|
||||
|
||||
LOG_IN,
|
||||
|
||||
TOTP_CODE
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
import fr.xephi.authme.task.MessageTask;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Represents a player which is not logged in and keeps track of certain states (like OP status, flying)
|
||||
* which may be revoked from the player until he has logged in or registered.
|
||||
*/
|
||||
public class LimboPlayer {
|
||||
|
||||
public static final float DEFAULT_WALK_SPEED = 0.2f;
|
||||
public static final float DEFAULT_FLY_SPEED = 0.1f;
|
||||
|
||||
private final boolean canFly;
|
||||
private final boolean operator;
|
||||
private final Collection<UserGroup> groups;
|
||||
private final Location loc;
|
||||
private final float walkSpeed;
|
||||
private final float flySpeed;
|
||||
private BukkitTask timeoutTask = null;
|
||||
private MessageTask messageTask = null;
|
||||
private LimboPlayerState state = LimboPlayerState.PASSWORD_REQUIRED;
|
||||
|
||||
public LimboPlayer(Location loc, boolean operator, Collection<UserGroup> groups, boolean fly, float walkSpeed,
|
||||
float flySpeed) {
|
||||
this.loc = loc;
|
||||
this.operator = operator;
|
||||
this.groups = new ArrayList<>(groups); // prevent bug #2413
|
||||
this.canFly = fly;
|
||||
this.walkSpeed = walkSpeed;
|
||||
this.flySpeed = flySpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the player's original location.
|
||||
*
|
||||
* @return The player's location
|
||||
*/
|
||||
public Location getLocation() {
|
||||
return loc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the player is an operator or not (i.e. whether he is an OP).
|
||||
*
|
||||
* @return True if the player has OP status, false otherwise
|
||||
*/
|
||||
public boolean isOperator() {
|
||||
return operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the player's permissions groups.
|
||||
*
|
||||
* @return The permissions groups the player belongs to
|
||||
*/
|
||||
public Collection<UserGroup> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public boolean isCanFly() {
|
||||
return canFly;
|
||||
}
|
||||
|
||||
public float getWalkSpeed() {
|
||||
return walkSpeed;
|
||||
}
|
||||
|
||||
public float getFlySpeed() {
|
||||
return flySpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timeout task, which kicks the player if he hasn't registered or logged in
|
||||
* after a configurable amount of time.
|
||||
*
|
||||
* @return The timeout task associated to the player
|
||||
*/
|
||||
public BukkitTask getTimeoutTask() {
|
||||
return timeoutTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeout task of the player. The timeout task kicks the player after a configurable
|
||||
* amount of time if he hasn't logged in or registered.
|
||||
*
|
||||
* @param timeoutTask The task to set
|
||||
*/
|
||||
public void setTimeoutTask(BukkitTask timeoutTask) {
|
||||
if (this.timeoutTask != null) {
|
||||
this.timeoutTask.cancel();
|
||||
}
|
||||
this.timeoutTask = timeoutTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the message task reminding the player to log in or register.
|
||||
*
|
||||
* @return The task responsible for sending the message regularly
|
||||
*/
|
||||
public MessageTask getMessageTask() {
|
||||
return messageTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the messages task responsible for telling the player to log in or register.
|
||||
*
|
||||
* @param messageTask The message task to set
|
||||
*/
|
||||
public void setMessageTask(MessageTask messageTask) {
|
||||
if (this.messageTask != null) {
|
||||
this.messageTask.cancel();
|
||||
}
|
||||
this.messageTask = messageTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all tasks associated to the player.
|
||||
*/
|
||||
public void clearTasks() {
|
||||
setMessageTask(null);
|
||||
setTimeoutTask(null);
|
||||
}
|
||||
|
||||
public LimboPlayerState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(LimboPlayerState state) {
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
public enum LimboPlayerState {
|
||||
|
||||
PASSWORD_REQUIRED,
|
||||
|
||||
TOTP_REQUIRED
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
import fr.xephi.authme.data.auth.PlayerCache;
|
||||
import fr.xephi.authme.data.captcha.RegistrationCaptchaManager;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.message.Messages;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.RegistrationSettings;
|
||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||
import fr.xephi.authme.task.MessageTask;
|
||||
import fr.xephi.authme.task.TimeoutTask;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND;
|
||||
|
||||
/**
|
||||
* Registers tasks associated with a LimboPlayer.
|
||||
*/
|
||||
class LimboPlayerTaskManager {
|
||||
|
||||
@Inject
|
||||
private Messages messages;
|
||||
|
||||
@Inject
|
||||
private Settings settings;
|
||||
|
||||
@Inject
|
||||
private BukkitService bukkitService;
|
||||
|
||||
@Inject
|
||||
private PlayerCache playerCache;
|
||||
|
||||
@Inject
|
||||
private RegistrationCaptchaManager registrationCaptchaManager;
|
||||
|
||||
LimboPlayerTaskManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link MessageTask} for the given player name.
|
||||
*
|
||||
* @param player the player
|
||||
* @param limbo the associated limbo player of the player
|
||||
* @param messageType message type
|
||||
*/
|
||||
void registerMessageTask(Player player, LimboPlayer limbo, LimboMessageType messageType) {
|
||||
int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL);
|
||||
MessageResult result = getMessageKey(player.getName(), messageType);
|
||||
if (interval > 0) {
|
||||
String[] joinMessage = messages.retrieveSingle(player, result.messageKey, result.args).split("\n");
|
||||
MessageTask messageTask = new MessageTask(player, joinMessage);
|
||||
bukkitService.runTaskTimer(messageTask, 2 * TICKS_PER_SECOND, interval * TICKS_PER_SECOND);
|
||||
limbo.setMessageTask(messageTask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link TimeoutTask} for the given player according to the configuration.
|
||||
*
|
||||
* @param player the player to register a timeout task for
|
||||
* @param limbo the associated limbo player
|
||||
*/
|
||||
void registerTimeoutTask(Player player, LimboPlayer limbo) {
|
||||
final int timeout = settings.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND;
|
||||
if (timeout > 0) {
|
||||
String message = messages.retrieveSingle(player, MessageKey.LOGIN_TIMEOUT_ERROR);
|
||||
BukkitTask task = bukkitService.runTaskLater(new TimeoutTask(player, message, playerCache), timeout);
|
||||
limbo.setTimeoutTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to set the muted flag on a message task.
|
||||
*
|
||||
* @param task the task to modify (or null)
|
||||
* @param isMuted the value to set if task is not null
|
||||
*/
|
||||
static void setMuted(MessageTask task, boolean isMuted) {
|
||||
if (task != null) {
|
||||
task.setMuted(isMuted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate message key according to the registration status and settings.
|
||||
*
|
||||
* @param name the player's name
|
||||
* @param messageType the message to show
|
||||
* @return the message key to display to the user
|
||||
*/
|
||||
private MessageResult getMessageKey(String name, LimboMessageType messageType) {
|
||||
if (messageType == LimboMessageType.LOG_IN) {
|
||||
return new MessageResult(MessageKey.LOGIN_MESSAGE);
|
||||
} else if (messageType == LimboMessageType.TOTP_CODE) {
|
||||
return new MessageResult(MessageKey.TWO_FACTOR_CODE_REQUIRED);
|
||||
} else if (registrationCaptchaManager.isCaptchaRequired(name)) {
|
||||
final String captchaCode = registrationCaptchaManager.getCaptchaCodeOrGenerateNew(name);
|
||||
return new MessageResult(MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED, captchaCode);
|
||||
} else {
|
||||
return new MessageResult(MessageKey.REGISTER_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MessageResult {
|
||||
private final MessageKey messageKey;
|
||||
private final String[] args;
|
||||
|
||||
MessageResult(MessageKey messageKey, String... args) {
|
||||
this.messageKey = messageKey;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.SpawnLoader;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Locale;
|
||||
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.
|
||||
*/
|
||||
public class LimboService {
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(LimboService.class);
|
||||
|
||||
private final Map<String, LimboPlayer> entries = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
private Settings settings;
|
||||
|
||||
@Inject
|
||||
private LimboPlayerTaskManager taskManager;
|
||||
|
||||
@Inject
|
||||
private LimboServiceHelper helper;
|
||||
|
||||
@Inject
|
||||
private LimboPersistence persistence;
|
||||
|
||||
@Inject
|
||||
private AuthGroupHandler authGroupHandler;
|
||||
|
||||
@Inject
|
||||
private SpawnLoader spawnLoader;
|
||||
|
||||
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(Locale.ROOT);
|
||||
|
||||
LimboPlayer limboFromDisk = persistence.getLimboPlayer(player);
|
||||
if (limboFromDisk != null) {
|
||||
logger.debug("LimboPlayer for `{0}` already exists on disk", name);
|
||||
}
|
||||
|
||||
LimboPlayer existingLimbo = entries.remove(name);
|
||||
if (existingLimbo != null) {
|
||||
existingLimbo.clearTasks();
|
||||
logger.debug("LimboPlayer for `{0}` already present in memory", name);
|
||||
}
|
||||
|
||||
Location location = spawnLoader.getPlayerLocationOrSpawn(player);
|
||||
LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk);
|
||||
limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered, location), limboPlayer);
|
||||
|
||||
taskManager.registerMessageTask(player, limboPlayer,
|
||||
isRegistered ? LimboMessageType.LOG_IN : LimboMessageType.REGISTER);
|
||||
taskManager.registerTimeoutTask(player, limboPlayer);
|
||||
helper.revokeLimboStates(player);
|
||||
authGroupHandler.setGroup(player, limboPlayer,
|
||||
isRegistered ? AuthGroupType.REGISTERED_UNAUTHENTICATED : AuthGroupType.UNREGISTERED);
|
||||
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(Locale.ROOT));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(Locale.ROOT));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.data.limbo.AuthGroupHandler}.
|
||||
*
|
||||
* @param player the player whose data should be restored
|
||||
*/
|
||||
public void restoreData(Player player) {
|
||||
String lowerName = player.getName().toLowerCase(Locale.ROOT);
|
||||
LimboPlayer limbo = entries.remove(lowerName);
|
||||
|
||||
if (limbo == null) {
|
||||
logger.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();
|
||||
logger.debug("Restored LimboPlayer stats for `{0}`", lowerName);
|
||||
persistence.removeLimboPlayer(player);
|
||||
}
|
||||
authGroupHandler.setGroup(player, limbo, AuthGroupType.LOGGED_IN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
Optional<LimboPlayer> limboPlayer = getLimboOrLogError(player, "reset tasks");
|
||||
limboPlayer.ifPresent(limbo -> {
|
||||
taskManager.registerTimeoutTask(player, limbo);
|
||||
taskManager.registerMessageTask(player, limbo, LimboMessageType.LOG_IN);
|
||||
});
|
||||
authGroupHandler.setGroup(player, limboPlayer.orElse(null), AuthGroupType.REGISTERED_UNAUTHENTICATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the message task associated with the player's LimboPlayer.
|
||||
*
|
||||
* @param player the player to set a new message task for
|
||||
* @param messageType the message to show for the limbo player
|
||||
*/
|
||||
public void resetMessageTask(Player player, LimboMessageType messageType) {
|
||||
getLimboOrLogError(player, "reset message task")
|
||||
.ifPresent(limbo -> taskManager.registerMessageTask(player, limbo, messageType));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(Locale.ROOT));
|
||||
if (limbo == null) {
|
||||
logger.debug("No LimboPlayer found for `{0}`. Action: {1}", player.getName(), context);
|
||||
}
|
||||
return Optional.ofNullable(limbo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
import fr.xephi.authme.permission.PermissionsManager;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.LimboSettings;
|
||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static fr.xephi.authme.util.Utils.isCollectionEmpty;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
/**
|
||||
* Helper class for the LimboService.
|
||||
*/
|
||||
class LimboServiceHelper {
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(LimboServiceHelper.class);
|
||||
|
||||
@Inject
|
||||
private PermissionsManager permissionsManager;
|
||||
|
||||
@Inject
|
||||
private Settings settings;
|
||||
|
||||
/**
|
||||
* Creates a LimboPlayer with the given player's details.
|
||||
*
|
||||
* @param player the player to process
|
||||
* @param isRegistered whether the player is registered
|
||||
* @param location the player location
|
||||
* @return limbo player with the player's data
|
||||
*/
|
||||
LimboPlayer createLimboPlayer(Player player, boolean isRegistered, Location location) {
|
||||
// For safety reasons an unregistered player should not have OP status after registration
|
||||
boolean isOperator = isRegistered && player.isOp();
|
||||
boolean flyEnabled = player.getAllowFlight();
|
||||
float walkSpeed = player.getWalkSpeed();
|
||||
float flySpeed = player.getFlySpeed();
|
||||
Collection<UserGroup> playerGroups = permissionsManager.hasGroupSupport()
|
||||
? permissionsManager.getGroups(player) : Collections.emptyList();
|
||||
|
||||
List<String> groupNames = playerGroups.stream()
|
||||
.map(UserGroup::getGroupName)
|
||||
.collect(toList());
|
||||
|
||||
logger.debug("Player `{0}` has groups `{1}`", player.getName(), String.join(", ", groupNames));
|
||||
return new LimboPlayer(location, isOperator, playerGroups, flyEnabled, walkSpeed, flySpeed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the data that is saved in a LimboPlayer from the player.
|
||||
* <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.data.limbo.AuthGroupHandler}.
|
||||
*
|
||||
* @param player the player to set defaults to
|
||||
*/
|
||||
void revokeLimboStates(Player player) {
|
||||
player.setOp(false);
|
||||
settings.getProperty(LimboSettings.RESTORE_ALLOW_FLIGHT)
|
||||
.processPlayer(player);
|
||||
|
||||
if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)) {
|
||||
player.setFlySpeed(0.0f);
|
||||
player.setWalkSpeed(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two existing LimboPlayer instances of a player. Merging is done the following way:
|
||||
* <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>groups, location</code>: from old limbo if not empty/null, otherwise from new limbo</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param newLimbo the new limbo player
|
||||
* @param oldLimbo the old limbo player
|
||||
* @return merged limbo player if both arguments are not null, otherwise the first non-null argument
|
||||
*/
|
||||
LimboPlayer merge(LimboPlayer newLimbo, LimboPlayer oldLimbo) {
|
||||
if (newLimbo == null) {
|
||||
return oldLimbo;
|
||||
} else if (oldLimbo == null) {
|
||||
return newLimbo;
|
||||
}
|
||||
|
||||
boolean isOperator = newLimbo.isOperator() || oldLimbo.isOperator();
|
||||
boolean canFly = newLimbo.isCanFly() || oldLimbo.isCanFly();
|
||||
float flySpeed = Math.max(newLimbo.getFlySpeed(), oldLimbo.getFlySpeed());
|
||||
float walkSpeed = Math.max(newLimbo.getWalkSpeed(), oldLimbo.getWalkSpeed());
|
||||
Collection<UserGroup> groups = getLimboGroups(oldLimbo.getGroups(), newLimbo.getGroups());
|
||||
Location location = firstNotNull(oldLimbo.getLocation(), newLimbo.getLocation());
|
||||
|
||||
return new LimboPlayer(location, isOperator, groups, canFly, walkSpeed, flySpeed);
|
||||
}
|
||||
|
||||
private static Location firstNotNull(Location first, Location second) {
|
||||
return first == null ? second : first;
|
||||
}
|
||||
|
||||
private Collection<UserGroup> getLimboGroups(Collection<UserGroup> oldLimboGroups,
|
||||
Collection<UserGroup> newLimboGroups) {
|
||||
logger.debug("Limbo merge: new and old groups are `{0}` and `{1}`", newLimboGroups, oldLimboGroups);
|
||||
return isCollectionEmpty(oldLimboGroups) ? newLimboGroups : oldLimboGroups;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class UserGroup {
|
||||
|
||||
private String groupName;
|
||||
private Map<String, String> contextMap;
|
||||
|
||||
public UserGroup(String groupName) {
|
||||
this.groupName = groupName;
|
||||
}
|
||||
|
||||
public UserGroup(String groupName, Map<String, String> contextMap) {
|
||||
this.groupName = groupName;
|
||||
this.contextMap = contextMap;
|
||||
}
|
||||
|
||||
public String getGroupName() {
|
||||
return groupName;
|
||||
}
|
||||
|
||||
public Map<String, String> getContextMap() {
|
||||
return contextMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
UserGroup userGroup = (UserGroup) o;
|
||||
return Objects.equals(groupName, userGroup.groupName)
|
||||
&& Objects.equals(contextMap, userGroup.contextMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(groupName, contextMap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Possible types to restore the walk and fly speed from LimboPlayer
|
||||
* back to Bukkit Player.
|
||||
*/
|
||||
public enum WalkFlySpeedRestoreType {
|
||||
|
||||
/**
|
||||
* Restores from LimboPlayer to Player.
|
||||
*/
|
||||
RESTORE {
|
||||
@Override
|
||||
public void restoreFlySpeed(Player player, LimboPlayer limbo) {
|
||||
logger.debug(() -> "Restoring fly speed for LimboPlayer " + player.getName() + " to "
|
||||
+ limbo.getFlySpeed() + " (RESTORE mode)");
|
||||
player.setFlySpeed(limbo.getFlySpeed());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
|
||||
logger.debug(() -> "Restoring walk speed for LimboPlayer " + player.getName() + " to "
|
||||
+ limbo.getWalkSpeed() + " (RESTORE mode)");
|
||||
player.setWalkSpeed(limbo.getWalkSpeed());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores from LimboPlayer, using the default speed if the speed on LimboPlayer is 0.
|
||||
*/
|
||||
RESTORE_NO_ZERO {
|
||||
@Override
|
||||
public void restoreFlySpeed(Player player, LimboPlayer limbo) {
|
||||
float limboFlySpeed = limbo.getFlySpeed();
|
||||
if (limboFlySpeed > 0.01f) {
|
||||
logger.debug(() -> "Restoring fly speed for LimboPlayer " + player.getName() + " to "
|
||||
+ limboFlySpeed + " (RESTORE_NO_ZERO mode)");
|
||||
player.setFlySpeed(limboFlySpeed);
|
||||
} else {
|
||||
logger.debug(() -> "Restoring fly speed for LimboPlayer " + player.getName()
|
||||
+ " to DEFAULT, it was 0! (RESTORE_NO_ZERO mode)");
|
||||
player.setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
|
||||
float limboWalkSpeed = limbo.getWalkSpeed();
|
||||
if (limboWalkSpeed > 0.01f) {
|
||||
logger.debug(() -> "Restoring walk speed for LimboPlayer " + player.getName() + " to "
|
||||
+ limboWalkSpeed + " (RESTORE_NO_ZERO mode)");
|
||||
player.setWalkSpeed(limboWalkSpeed);
|
||||
} else {
|
||||
logger.debug(() -> "Restoring walk speed for LimboPlayer " + player.getName() + ""
|
||||
+ " to DEFAULT, it was 0! (RESTORE_NO_ZERO mode)");
|
||||
player.setWalkSpeed(LimboPlayer.DEFAULT_WALK_SPEED);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Uses the max speed of Player (current speed) and the LimboPlayer.
|
||||
*/
|
||||
MAX_RESTORE {
|
||||
@Override
|
||||
public void restoreFlySpeed(Player player, LimboPlayer limbo) {
|
||||
float newSpeed = Math.max(player.getFlySpeed(), limbo.getFlySpeed());
|
||||
logger.debug(() -> "Restoring fly speed for LimboPlayer " + player.getName() + " to " + newSpeed
|
||||
+ " (Current: " + player.getFlySpeed() + ", Limbo: " + limbo.getFlySpeed() + ") (MAX_RESTORE mode)");
|
||||
player.setFlySpeed(newSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
|
||||
float newSpeed = Math.max(player.getWalkSpeed(), limbo.getWalkSpeed());
|
||||
logger.debug(() -> "Restoring walk speed for LimboPlayer " + player.getName() + " to " + newSpeed
|
||||
+ " (Current: " + player.getWalkSpeed() + ", Limbo: " + limbo.getWalkSpeed() + ") (MAX_RESTORE mode)");
|
||||
player.setWalkSpeed(newSpeed);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Always sets the default speed to the player.
|
||||
*/
|
||||
DEFAULT {
|
||||
@Override
|
||||
public void restoreFlySpeed(Player player, LimboPlayer limbo) {
|
||||
logger.debug(() -> "Restoring fly speed for LimboPlayer " + player.getName()
|
||||
+ " to DEFAULT (DEFAULT mode)");
|
||||
player.setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
|
||||
logger.debug(() -> "Restoring walk speed for LimboPlayer " + player.getName()
|
||||
+ " to DEFAULT (DEFAULT mode)");
|
||||
player.setWalkSpeed(LimboPlayer.DEFAULT_WALK_SPEED);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ConsoleLogger logger = ConsoleLoggerFactory.get(WalkFlySpeedRestoreType.class);
|
||||
|
||||
/**
|
||||
* Restores the fly speed from Limbo to Player according to the restoration type.
|
||||
*
|
||||
* @param player the player to modify
|
||||
* @param limbo the limbo player to read from
|
||||
*/
|
||||
public abstract void restoreFlySpeed(Player player, LimboPlayer limbo);
|
||||
|
||||
/**
|
||||
* Restores the walk speed from Limbo to Player according to the restoration type.
|
||||
*
|
||||
* @param player the player to modify
|
||||
* @param limbo the limbo player to read from
|
||||
*/
|
||||
public abstract void restoreWalkSpeed(Player player, LimboPlayer limbo);
|
||||
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
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.output.ConsoleLoggerFactory;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.LimboSettings;
|
||||
import fr.xephi.authme.util.FileUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Persistence handler for LimboPlayer objects by distributing the objects to store
|
||||
* in various segments (buckets) based on the start of the player's UUID.
|
||||
*/
|
||||
class DistributedFilesPersistenceHandler implements LimboPersistenceHandler {
|
||||
|
||||
private static final Type LIMBO_MAP_TYPE = new TypeToken<Map<String, LimboPlayer>>(){}.getType();
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(DistributedFilesPersistenceHandler.class);
|
||||
private final File cacheFolder;
|
||||
private final Gson gson;
|
||||
private final SegmentNameBuilder segmentNameBuilder;
|
||||
|
||||
@Inject
|
||||
DistributedFilesPersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService, Settings settings) {
|
||||
cacheFolder = new File(dataFolder, "playerdata");
|
||||
FileUtils.createDirectory(cacheFolder);
|
||||
|
||||
gson = new GsonBuilder()
|
||||
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
|
||||
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService))
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
segmentNameBuilder = new SegmentNameBuilder(settings.getProperty(LimboSettings.DISTRIBUTION_SIZE));
|
||||
|
||||
convertOldDataToCurrentSegmentScheme();
|
||||
deleteEmptyFiles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LimboPlayer getLimboPlayer(Player player) {
|
||||
String uuid = player.getUniqueId().toString();
|
||||
File file = getPlayerSegmentFile(uuid);
|
||||
Map<String, LimboPlayer> entries = readLimboPlayers(file);
|
||||
return entries == null ? null : entries.get(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveLimboPlayer(Player player, LimboPlayer limbo) {
|
||||
String uuid = player.getUniqueId().toString();
|
||||
File file = getPlayerSegmentFile(uuid);
|
||||
|
||||
Map<String, LimboPlayer> entries = null;
|
||||
if (file.exists()) {
|
||||
entries = readLimboPlayers(file);
|
||||
} else {
|
||||
FileUtils.create(file);
|
||||
}
|
||||
/* intentionally separate if */
|
||||
if (entries == null) {
|
||||
entries = new HashMap<>();
|
||||
}
|
||||
|
||||
entries.put(uuid, limbo);
|
||||
saveEntries(entries, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLimboPlayer(Player player) {
|
||||
String uuid = player.getUniqueId().toString();
|
||||
File file = getPlayerSegmentFile(uuid);
|
||||
if (file.exists()) {
|
||||
Map<String, LimboPlayer> entries = readLimboPlayers(file);
|
||||
if (entries != null && entries.remove(uuid) != null) {
|
||||
saveEntries(entries, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LimboPersistenceType getType() {
|
||||
return LimboPersistenceType.DISTRIBUTED_FILES;
|
||||
}
|
||||
|
||||
private void saveEntries(Map<String, LimboPlayer> entries, File file) {
|
||||
try (FileWriter fw = new FileWriter(file)) {
|
||||
gson.toJson(entries, fw);
|
||||
} catch (Exception e) {
|
||||
logger.logException("Could not write to '" + file + "':", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, LimboPlayer> readLimboPlayers(File file) {
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return gson.fromJson(Files.asCharSource(file, StandardCharsets.UTF_8).read(), LIMBO_MAP_TYPE);
|
||||
} catch (Exception e) {
|
||||
logger.logException("Failed reading '" + file + "':", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private File getPlayerSegmentFile(String uuid) {
|
||||
String segment = segmentNameBuilder.createSegmentName(uuid);
|
||||
return getSegmentFile(segment);
|
||||
}
|
||||
|
||||
private File getSegmentFile(String segmentId) {
|
||||
return new File(cacheFolder, segmentId + "-limbo.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads segment files in the cache folder that don't correspond to the current segmenting scheme
|
||||
* and migrates the data into files of the current segments. This allows a player to change the
|
||||
* segment size without any loss of data.
|
||||
*/
|
||||
private void convertOldDataToCurrentSegmentScheme() {
|
||||
String currentPrefix = segmentNameBuilder.getPrefix();
|
||||
File[] files = listFiles(cacheFolder);
|
||||
Map<String, LimboPlayer> allLimboPlayers = new HashMap<>();
|
||||
List<File> migratedFiles = new ArrayList<>();
|
||||
|
||||
for (File file : files) {
|
||||
if (isLimboJsonFile(file) && !file.getName().startsWith(currentPrefix)) {
|
||||
Map<String, LimboPlayer> data = readLimboPlayers(file);
|
||||
if (data != null) {
|
||||
allLimboPlayers.putAll(data);
|
||||
migratedFiles.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allLimboPlayers.isEmpty()) {
|
||||
saveToNewSegments(allLimboPlayers);
|
||||
migratedFiles.forEach(FileUtils::delete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the LimboPlayer data read from old segmenting schemes into the current segmenting scheme.
|
||||
*
|
||||
* @param limbosFromOldSegments the limbo players to store into the current segment files
|
||||
*/
|
||||
private void saveToNewSegments(Map<String, LimboPlayer> limbosFromOldSegments) {
|
||||
Map<String, Map<String, LimboPlayer>> limboBySegment = groupBySegment(limbosFromOldSegments);
|
||||
|
||||
logger.info("Saving " + limbosFromOldSegments.size() + " LimboPlayers from old segments into "
|
||||
+ limboBySegment.size() + " current segments");
|
||||
for (Map.Entry<String, Map<String, LimboPlayer>> entry : limboBySegment.entrySet()) {
|
||||
File file = getSegmentFile(entry.getKey());
|
||||
Map<String, LimboPlayer> limbosToSave = Optional.ofNullable(readLimboPlayers(file))
|
||||
.orElseGet(HashMap::new);
|
||||
limbosToSave.putAll(entry.getValue());
|
||||
saveEntries(limbosToSave, file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Map of UUID to LimboPlayers to a 2-dimensional Map of LimboPlayers by segment ID and UUID.
|
||||
* {@code Map(uuid -> LimboPlayer) to Map(segment -> Map(uuid -> LimboPlayer))}
|
||||
*
|
||||
* @param readLimboPlayers the limbo players to order by segment
|
||||
* @return limbo players ordered by segment ID and associated player UUID
|
||||
*/
|
||||
private Map<String, Map<String, LimboPlayer>> groupBySegment(Map<String, LimboPlayer> readLimboPlayers) {
|
||||
Map<String, Map<String, LimboPlayer>> limboBySegment = new HashMap<>();
|
||||
for (Map.Entry<String, LimboPlayer> entry : readLimboPlayers.entrySet()) {
|
||||
String segmentId = segmentNameBuilder.createSegmentName(entry.getKey());
|
||||
limboBySegment.computeIfAbsent(segmentId, s -> new HashMap<>())
|
||||
.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return limboBySegment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes segment files that are empty.
|
||||
*/
|
||||
private void deleteEmptyFiles() {
|
||||
File[] files = listFiles(cacheFolder);
|
||||
|
||||
long deletedFiles = Arrays.stream(files)
|
||||
// typically the size is 2 because there's an empty JSON map: {}
|
||||
.filter(f -> isLimboJsonFile(f) && f.length() < 3)
|
||||
.peek(FileUtils::delete)
|
||||
.count();
|
||||
logger.debug("Limbo: Deleted {0} empty segment files", deletedFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file the file to check
|
||||
* @return true if it is a segment file storing Limbo data, false otherwise
|
||||
*/
|
||||
private static boolean isLimboJsonFile(File file) {
|
||||
String name = file.getName();
|
||||
return name.startsWith("seg") && name.endsWith("-limbo.json");
|
||||
}
|
||||
|
||||
private File[] listFiles(File folder) {
|
||||
File[] files = folder.listFiles();
|
||||
if (files == null) {
|
||||
logger.warning("Could not get files of '" + folder + "'");
|
||||
return new File[0];
|
||||
}
|
||||
return files;
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
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.output.ConsoleLoggerFactory;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.util.FileUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Saves LimboPlayer objects as JSON into individual files.
|
||||
*/
|
||||
class IndividualFilesPersistenceHandler implements LimboPersistenceHandler {
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(IndividualFilesPersistenceHandler.class);
|
||||
|
||||
private final Gson gson;
|
||||
private final File cacheDir;
|
||||
|
||||
@Inject
|
||||
IndividualFilesPersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) {
|
||||
cacheDir = new File(dataFolder, "playerdata");
|
||||
if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) {
|
||||
logger.warning("Failed to create playerdata directory '" + cacheDir + "'");
|
||||
}
|
||||
gson = new GsonBuilder()
|
||||
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
|
||||
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService))
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LimboPlayer getLimboPlayer(Player player) {
|
||||
String id = player.getUniqueId().toString();
|
||||
File file = new File(cacheDir, id + File.separator + "data.json");
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String str = Files.asCharSource(file, StandardCharsets.UTF_8).read();
|
||||
return gson.fromJson(str, LimboPlayer.class);
|
||||
} catch (IOException e) {
|
||||
logger.logException("Could not read player data on disk for '" + player.getName() + "'", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveLimboPlayer(Player player, LimboPlayer limboPlayer) {
|
||||
String id = player.getUniqueId().toString();
|
||||
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) {
|
||||
logger.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 = player.getUniqueId().toString();
|
||||
File file = new File(cacheDir, id);
|
||||
if (file.exists()) {
|
||||
FileUtils.purgeDirectory(file);
|
||||
FileUtils.delete(file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LimboPersistenceType getType() {
|
||||
return LimboPersistenceType.INDIVIDUAL_FILES;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import ch.jalu.injector.factory.Factory;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import fr.xephi.authme.initialization.SettingsDependent;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
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 ConsoleLogger logger = ConsoleLoggerFactory.get(LimboPersistence.class);
|
||||
|
||||
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) {
|
||||
logger.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) {
|
||||
logger.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) {
|
||||
logger.logException("Could not remove LimboPlayer for '" + player.getName() + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(Settings settings) {
|
||||
LimboPersistenceType persistenceType = settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE);
|
||||
// If we're changing from an existing handler, output a quick hint that nothing is converted.
|
||||
if (handler != null && handler.getType() != persistenceType) {
|
||||
logger.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,34 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
/**
|
||||
* Types of persistence for LimboPlayer objects.
|
||||
*/
|
||||
public enum LimboPersistenceType {
|
||||
|
||||
/** Store each LimboPlayer in a separate file. */
|
||||
INDIVIDUAL_FILES(IndividualFilesPersistenceHandler.class),
|
||||
|
||||
/** Store LimboPlayers distributed in a configured number of files. */
|
||||
DISTRIBUTED_FILES(DistributedFilesPersistenceHandler.class),
|
||||
|
||||
/** No persistence to disk. */
|
||||
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,163 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
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.data.limbo.UserGroup;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.GROUPS;
|
||||
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;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
/**
|
||||
* Converts a JsonElement to a LimboPlayer.
|
||||
*/
|
||||
class LimboPlayerDeserializer implements JsonDeserializer<LimboPlayer> {
|
||||
|
||||
private static final String GROUP_LEGACY = "group";
|
||||
private static final String CONTEXT_MAP = "contextMap";
|
||||
private static final String GROUP_NAME = "groupName";
|
||||
|
||||
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);
|
||||
|
||||
Collection<UserGroup> groups = getLimboGroups(jsonObject);
|
||||
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, groups, 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() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param jsonObject LimboPlayer represented as JSON
|
||||
* @return The list of UserGroups create from JSON
|
||||
*/
|
||||
private static List<UserGroup> getLimboGroups(JsonObject jsonObject) {
|
||||
JsonElement element = jsonObject.get(GROUPS);
|
||||
if (element == null) {
|
||||
String legacyGroup = ofNullable(jsonObject.get(GROUP_LEGACY)).map(JsonElement::getAsString).orElse(null);
|
||||
return legacyGroup == null ? Collections.emptyList() :
|
||||
Collections.singletonList(new UserGroup(legacyGroup, null));
|
||||
}
|
||||
List<UserGroup> result = new ArrayList<>();
|
||||
JsonArray jsonArray = element.getAsJsonArray();
|
||||
for (JsonElement arrayElement : jsonArray) {
|
||||
if (!arrayElement.isJsonObject()) {
|
||||
result.add(new UserGroup(arrayElement.getAsString(), null));
|
||||
} else {
|
||||
JsonObject jsonGroup = arrayElement.getAsJsonObject();
|
||||
Map<String, String> contextMap = null;
|
||||
if (jsonGroup.has(CONTEXT_MAP)) {
|
||||
JsonElement contextMapJson = jsonGroup.get("contextMap");
|
||||
Type type = new TypeToken<Map<String, String>>() {
|
||||
}.getType();
|
||||
contextMap = new Gson().fromJson(contextMapJson.getAsString(), type);
|
||||
}
|
||||
|
||||
String groupName = jsonGroup.get(GROUP_NAME).getAsString();
|
||||
result.add(new UserGroup(groupName, contextMap));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
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;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 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 GROUPS = "groups";
|
||||
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";
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
|
||||
@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);
|
||||
|
||||
List<JsonObject> groups = limboPlayer.getGroups().stream().map(g -> {
|
||||
JsonObject jsonGroup = new JsonObject();
|
||||
jsonGroup.addProperty("groupName", g.getGroupName());
|
||||
if (g.getContextMap() != null) {
|
||||
jsonGroup.addProperty("contextMap", GSON.toJson(g.getContextMap()));
|
||||
}
|
||||
return jsonGroup;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
JsonArray jsonGroups = new JsonArray();
|
||||
groups.forEach(jsonGroups::add);
|
||||
obj.add(GROUPS, jsonGroups);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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,73 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Creates segment names for {@link DistributedFilesPersistenceHandler}.
|
||||
*/
|
||||
class SegmentNameBuilder {
|
||||
|
||||
private final int length;
|
||||
private final int distribution;
|
||||
private final String prefix;
|
||||
private final Map<Character, Character> charToSegmentChar;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param partition the segment configuration
|
||||
*/
|
||||
SegmentNameBuilder(SegmentSize partition) {
|
||||
this.length = partition.getLength();
|
||||
this.distribution = partition.getDistribution();
|
||||
this.prefix = "seg" + partition.getTotalSegments() + "-";
|
||||
this.charToSegmentChar = buildCharMap(distribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the segment ID for the given UUID.
|
||||
*
|
||||
* @param uuid the player's uuid to get the segment for
|
||||
* @return id the uuid belongs to
|
||||
*/
|
||||
String createSegmentName(String uuid) {
|
||||
if (distribution == 16) {
|
||||
return prefix + uuid.substring(0, length);
|
||||
} else {
|
||||
return prefix + buildSegmentName(uuid.substring(0, length).toCharArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the prefix used for the current segment configuration
|
||||
*/
|
||||
String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
private String buildSegmentName(char[] chars) {
|
||||
if (chars.length == 1) {
|
||||
return String.valueOf(charToSegmentChar.get(chars[0]));
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(chars.length);
|
||||
for (char chr : chars) {
|
||||
sb.append(charToSegmentChar.get(chr));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static Map<Character, Character> buildCharMap(int distribution) {
|
||||
final char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
final int divisor = 16 / distribution;
|
||||
|
||||
Map<Character, Character> charToSegmentChar = new HashMap<>();
|
||||
for (int i = 0; i < hexChars.length; ++i) {
|
||||
int mappedChar = i / divisor;
|
||||
charToSegmentChar.put(hexChars[i], hexChars[mappedChar]);
|
||||
}
|
||||
return charToSegmentChar;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
/**
|
||||
* Configuration for the total number of segments to use.
|
||||
* <p>
|
||||
* The {@link DistributedFilesPersistenceHandler} reduces the number of files by assigning each UUID
|
||||
* to a segment. This enum allows to define how many segments the UUIDs should be distributed in.
|
||||
* <p>
|
||||
* Segments are defined by a <b>distribution</b> and a <b>length.</b> The distribution defines
|
||||
* to how many outputs a single hexadecimal characters should be mapped. So e.g. a distribution
|
||||
* of 3 means that all hexadecimal characters 0-f should be distributed over three different
|
||||
* outputs evenly. The {@link SegmentNameBuilder} simply uses hexadecimal characters as outputs,
|
||||
* so e.g. with a distribution of 3 all hex characters 0-f are mapped to 0, 1, or 2.
|
||||
* <p>
|
||||
* To ensure an even distribution the segments must be powers of 2. Trivially, to implement a
|
||||
* distribution of 16, the same character may be returned as was input (since 0-f make up 16
|
||||
* characters). A distribution of 1, on the other hand, means that the same output is returned
|
||||
* regardless of the input character.
|
||||
* <p>
|
||||
* The <b>length</b> parameter defines how many characters of a player's UUID should be used to
|
||||
* create the segment ID. In other words, with a distribution of 2 and a length of 3, the first
|
||||
* three characters of the UUID are taken into consideration, each mapped to one of two possible
|
||||
* characters. For instance, a UUID starting with "0f5c9321" may yield the segment ID "010."
|
||||
* Such a segment ID defines in which file the given UUID can be found and stored.
|
||||
* <p>
|
||||
* The number of segments such a configuration yields is computed as {@code distribution ^ length},
|
||||
* since distribution defines how many outputs there are per digit, and length defines the number
|
||||
* of digits. For instance, a distribution of 2 and a length of 3 will yield segment IDs 000, 001,
|
||||
* 010, 011, 100, 101, 110 and 111 (i.e. all binary numbers from 0 to 7).
|
||||
* <p>
|
||||
* There are multiple possibilities to achieve certain segment totals, e.g. 8 different segments
|
||||
* may be created by setting distribution to 8 and length to 1, or distr. to 2 and length to 3.
|
||||
* Where possible, prefer a length of 1 (no string concatenation required) or a distribution of
|
||||
* 16 (no remapping of the characters required).
|
||||
*/
|
||||
public enum SegmentSize {
|
||||
|
||||
/** 1. */
|
||||
ONE(1, 1),
|
||||
|
||||
// /** 2. */
|
||||
// TWO(2, 1),
|
||||
|
||||
/** 4. */
|
||||
FOUR(4, 1),
|
||||
|
||||
/** 8. */
|
||||
EIGHT(8, 1),
|
||||
|
||||
/** 16. */
|
||||
SIXTEEN(16, 1),
|
||||
|
||||
/** 32. */
|
||||
THIRTY_TWO(2, 5),
|
||||
|
||||
/** 64. */
|
||||
SIXTY_FOUR(4, 3),
|
||||
|
||||
/** 128. */
|
||||
ONE_TWENTY(2, 7),
|
||||
|
||||
/** 256. */
|
||||
TWO_FIFTY(16, 2);
|
||||
|
||||
private final int distribution;
|
||||
private final int length;
|
||||
|
||||
SegmentSize(int distribution, int length) {
|
||||
this.distribution = distribution;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the distribution size per character, i.e. how many possible outputs there are
|
||||
* for any hexadecimal character
|
||||
*/
|
||||
public int getDistribution() {
|
||||
return distribution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of characters from a UUID that should be used to create a segment ID
|
||||
*/
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of segments to which this configuration will distribute all UUIDs
|
||||
*/
|
||||
public int getTotalSegments() {
|
||||
return (int) Math.pow(distribution, length);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user