Skip to content

Commit 8557621

Browse files
committed
#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
1 parent 1678901 commit 8557621

16 files changed

+733
-28
lines changed

.checkstyle.xml

+1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
<module name="EmptyCatchBlock">
160160
<property name="exceptionVariableName" value="ignore|ignored"/>
161161
</module>
162+
<module name="MissingOverride"/>
162163
</module>
163164
<module name="FileTabCharacter"/>
164165
</module>

src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
import org.bukkit.command.CommandSender;
77

88
import javax.inject.Inject;
9-
import java.util.HashMap;
109
import java.util.List;
1110
import java.util.Map;
1211
import java.util.Set;
12+
import java.util.TreeMap;
1313

1414
/**
1515
* Debug command main.
@@ -47,7 +47,7 @@ private DebugSection getDebugSection(List<String> arguments) {
4747
// Lazy getter
4848
private Map<String, DebugSection> getSections() {
4949
if (sections == null) {
50-
Map<String, DebugSection> sections = new HashMap<>();
50+
Map<String, DebugSection> sections = new TreeMap<>();
5151
for (Class<? extends DebugSection> sectionClass : sectionClasses) {
5252
DebugSection section = debugSectionFactory.newInstance(sectionClass);
5353
sections.put(section.getName(), section);

src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java

+29-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import fr.xephi.authme.ConsoleLogger;
44
import fr.xephi.authme.data.limbo.LimboPlayer;
55
import fr.xephi.authme.data.limbo.LimboService;
6+
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
67
import fr.xephi.authme.service.BukkitService;
78
import org.bukkit.ChatColor;
89
import org.bukkit.command.CommandSender;
@@ -27,6 +28,9 @@ class LimboPlayerViewer implements DebugSection {
2728
@Inject
2829
private LimboService limboService;
2930

31+
@Inject
32+
private LimboPersistence limboPersistence;
33+
3034
@Inject
3135
private BukkitService bukkitService;
3236

@@ -50,22 +54,23 @@ public void execute(CommandSender sender, List<String> arguments) {
5054
return;
5155
}
5256

53-
LimboPlayer limbo = limboService.getLimboPlayer(arguments.get(0));
57+
LimboPlayer memoryLimbo = limboService.getLimboPlayer(arguments.get(0));
5458
Player player = bukkitService.getPlayerExact(arguments.get(0));
55-
if (limbo == null && player == null) {
59+
LimboPlayer diskLimbo = player != null ? limboPersistence.getLimboPlayer(player) : null;
60+
if (memoryLimbo == null && player == null) {
5661
sender.sendMessage("No limbo info and no player online with name '" + arguments.get(0) + "'");
5762
return;
5863
}
5964

60-
sender.sendMessage(ChatColor.GOLD + "Showing limbo / player info for '" + arguments.get(0) + "'");
61-
new InfoDisplayer(sender, limbo, player)
65+
sender.sendMessage(ChatColor.GOLD + "Showing disk limbo / limbo / player info for '" + arguments.get(0) + "'");
66+
new InfoDisplayer(sender, diskLimbo, memoryLimbo, player)
6267
.sendEntry("Is op", LimboPlayer::isOperator, Player::isOp)
6368
.sendEntry("Walk speed", LimboPlayer::getWalkSpeed, Player::getWalkSpeed)
6469
.sendEntry("Can fly", LimboPlayer::isCanFly, Player::getAllowFlight)
6570
.sendEntry("Fly speed", LimboPlayer::getFlySpeed, Player::getFlySpeed)
6671
.sendEntry("Location", l -> formatLocation(l.getLocation()), p -> formatLocation(p.getLocation()))
6772
.sendEntry("Group", LimboPlayer::getGroup, p -> "");
68-
sender.sendMessage("Note: group is only shown for LimboPlayer");
73+
sender.sendMessage("Note: group is not shown for Player. Use /authme debug groups");
6974
}
7075

7176
/**
@@ -102,25 +107,30 @@ private Set<String> getLimboKeys() {
102107
*/
103108
private static final class InfoDisplayer {
104109
private final CommandSender sender;
105-
private final Optional<LimboPlayer> limbo;
110+
private final Optional<LimboPlayer> diskLimbo;
111+
private final Optional<LimboPlayer> memoryLimbo;
106112
private final Optional<Player> player;
107113

108114
/**
109115
* Constructor.
110116
*
111117
* @param sender command sender to send the information to
112-
* @param limbo the limbo player to get data from
118+
* @param memoryLimbo the limbo player to get data from
113119
* @param player the player to get data from
114120
*/
115-
InfoDisplayer(CommandSender sender, LimboPlayer limbo, Player player) {
121+
InfoDisplayer(CommandSender sender, LimboPlayer diskLimbo, LimboPlayer memoryLimbo, Player player) {
116122
this.sender = sender;
117-
this.limbo = Optional.ofNullable(limbo);
123+
this.diskLimbo = Optional.ofNullable(diskLimbo);
124+
this.memoryLimbo = Optional.ofNullable(memoryLimbo);
118125
this.player = Optional.ofNullable(player);
119126

120-
if (limbo == null) {
127+
if (memoryLimbo == null) {
121128
sender.sendMessage("Note: no Limbo information available");
122-
} else if (player == null) {
129+
}
130+
if (player == null) {
123131
sender.sendMessage("Note: player is not online");
132+
} else if (diskLimbo == null) {
133+
sender.sendMessage("Note: no Limbo on disk available");
124134
}
125135
}
126136

@@ -138,10 +148,16 @@ <T> InfoDisplayer sendEntry(String title,
138148
Function<Player, T> playerGetter) {
139149
sender.sendMessage(
140150
title + ": "
141-
+ limbo.map(limboGetter).map(String::valueOf).orElse("--")
151+
+ getData(diskLimbo, limboGetter)
142152
+ " / "
143-
+ player.map(playerGetter).map(String::valueOf).orElse("--"));
153+
+ getData(memoryLimbo, limboGetter)
154+
+ " / "
155+
+ getData(player, playerGetter));
144156
return this;
145157
}
158+
159+
static <E, T> String getData(Optional<E> entity, Function<E, T> getter) {
160+
return entity.map(getter).map(String::valueOf).orElse(" -- ");
161+
}
146162
}
147163
}

src/main/java/fr/xephi/authme/data/limbo/LimboService.java

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package fr.xephi.authme.data.limbo;
22

33
import fr.xephi.authme.ConsoleLogger;
4+
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
45
import fr.xephi.authme.settings.Settings;
56
import org.bukkit.entity.Player;
67

@@ -28,7 +29,10 @@ public class LimboService {
2829
private LimboPlayerTaskManager taskManager;
2930

3031
@Inject
31-
private LimboServiceHelper limboServiceHelper;
32+
private LimboServiceHelper helper;
33+
34+
@Inject
35+
private LimboPersistence persistence;
3236

3337
LimboService() {
3438
}
@@ -42,19 +46,25 @@ public class LimboService {
4246
public void createLimboPlayer(Player player, boolean isRegistered) {
4347
final String name = player.getName().toLowerCase();
4448

49+
LimboPlayer limboFromDisk = persistence.getLimboPlayer(player);
50+
if (limboFromDisk != null) {
51+
ConsoleLogger.debug("LimboPlayer for `{0}` already exists on disk", name);
52+
}
53+
4554
LimboPlayer existingLimbo = entries.remove(name);
4655
if (existingLimbo != null) {
4756
existingLimbo.clearTasks();
48-
ConsoleLogger.debug("LimboPlayer for `{0}` was already present", name);
57+
ConsoleLogger.debug("LimboPlayer for `{0}` already present in memory", name);
4958
}
5059

51-
LimboPlayer limboPlayer = limboServiceHelper.merge(
52-
limboServiceHelper.createLimboPlayer(player, isRegistered), existingLimbo);
60+
LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk);
61+
limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered), limboPlayer);
5362

5463
taskManager.registerMessageTask(player, limboPlayer, isRegistered);
5564
taskManager.registerTimeoutTask(player, limboPlayer);
56-
limboServiceHelper.revokeLimboStates(player);
65+
helper.revokeLimboStates(player);
5766
entries.put(name, limboPlayer);
67+
persistence.saveLimboPlayer(player, limboPlayer);
5868
}
5969

6070
/**
@@ -98,6 +108,7 @@ public void restoreData(Player player) {
98108
settings.getProperty(RESTORE_WALK_SPEED).restoreWalkSpeed(player, limbo);
99109
limbo.clearTasks();
100110
ConsoleLogger.debug("Restored LimboPlayer stats for `{0}`", lowerName);
111+
persistence.removeLimboPlayer(player);
101112
}
102113
}
103114

src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ void revokeLimboStates(Player player) {
6969
* <ul>
7070
* <li><code>isOperator, allowFlight</code>: true if either limbo has true</li>
7171
* <li><code>flySpeed, walkSpeed</code>: maximum value of either limbo player</li>
72-
* <li><code>group</code>: from old limbo if not empty, otherwise from new limbo</li>
73-
* <li><code>location</code>: from old limbo</li>
72+
* <li><code>group, location</code>: from old limbo if not empty/null, otherwise from new limbo</li>
7473
* </ul>
7574
*
7675
* @param newLimbo the new limbo player
@@ -89,8 +88,9 @@ LimboPlayer merge(LimboPlayer newLimbo, LimboPlayer oldLimbo) {
8988
float flySpeed = Math.max(newLimbo.getFlySpeed(), oldLimbo.getFlySpeed());
9089
float walkSpeed = Math.max(newLimbo.getWalkSpeed(), oldLimbo.getWalkSpeed());
9190
String group = firstNotEmpty(newLimbo.getGroup(), oldLimbo.getGroup());
91+
Location location = firstNotNull(oldLimbo.getLocation(), newLimbo.getLocation());
9292

93-
return new LimboPlayer(oldLimbo.getLocation(), isOperator, group, canFly, walkSpeed, flySpeed);
93+
return new LimboPlayer(location, isOperator, group, canFly, walkSpeed, flySpeed);
9494
}
9595

9696
private static String firstNotEmpty(String newGroup, String oldGroup) {
@@ -100,4 +100,8 @@ private static String firstNotEmpty(String newGroup, String oldGroup) {
100100
}
101101
return oldGroup;
102102
}
103+
104+
private static Location firstNotNull(Location first, Location second) {
105+
return first == null ? second : first;
106+
}
103107
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package fr.xephi.authme.data.limbo.persistence;
2+
3+
import fr.xephi.authme.ConsoleLogger;
4+
import fr.xephi.authme.data.limbo.LimboPlayer;
5+
import fr.xephi.authme.initialization.SettingsDependent;
6+
import fr.xephi.authme.initialization.factory.Factory;
7+
import fr.xephi.authme.settings.Settings;
8+
import fr.xephi.authme.settings.properties.LimboSettings;
9+
import org.bukkit.entity.Player;
10+
11+
import javax.inject.Inject;
12+
13+
/**
14+
* Handles the persistence of LimboPlayers.
15+
*/
16+
public class LimboPersistence implements SettingsDependent {
17+
18+
private final Factory<LimboPersistenceHandler> handlerFactory;
19+
20+
private LimboPersistenceHandler handler;
21+
22+
@Inject
23+
LimboPersistence(Settings settings, Factory<LimboPersistenceHandler> handlerFactory) {
24+
this.handlerFactory = handlerFactory;
25+
reload(settings);
26+
}
27+
28+
/**
29+
* Retrieves the LimboPlayer for the given player if available.
30+
*
31+
* @param player the player to retrieve the LimboPlayer for
32+
* @return the player's limbo player, or null if not available
33+
*/
34+
public LimboPlayer getLimboPlayer(Player player) {
35+
try {
36+
return handler.getLimboPlayer(player);
37+
} catch (Exception e) {
38+
ConsoleLogger.logException("Could not get LimboPlayer for '" + player.getName() + "'", e);
39+
}
40+
return null;
41+
}
42+
43+
/**
44+
* Saves the given LimboPlayer for the provided player.
45+
*
46+
* @param player the player to save the LimboPlayer for
47+
* @param limbo the limbo player to save
48+
*/
49+
public void saveLimboPlayer(Player player, LimboPlayer limbo) {
50+
try {
51+
handler.saveLimboPlayer(player, limbo);
52+
} catch (Exception e) {
53+
ConsoleLogger.logException("Could not save LimboPlayer for '" + player.getName() + "'", e);
54+
}
55+
}
56+
57+
/**
58+
* Removes the LimboPlayer for the given player.
59+
*
60+
* @param player the player whose LimboPlayer should be removed
61+
*/
62+
public void removeLimboPlayer(Player player) {
63+
try {
64+
handler.removeLimboPlayer(player);
65+
} catch (Exception e) {
66+
ConsoleLogger.logException("Could not remove LimboPlayer for '" + player.getName() + "'", e);
67+
}
68+
}
69+
70+
@Override
71+
public void reload(Settings settings) {
72+
LimboPersistenceType persistenceType = settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE);
73+
if (handler == null || handler.getType() != persistenceType) {
74+
// If we're changing from an existing handler, output a quick hint that nothing is converted.
75+
if (handler != null) {
76+
ConsoleLogger.info("Limbo persistence type has changed! Note that the data is not converted.");
77+
}
78+
79+
handler = handlerFactory.newInstance(persistenceType.getImplementationClass());
80+
}
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package fr.xephi.authme.data.limbo.persistence;
2+
3+
import fr.xephi.authme.data.limbo.LimboPlayer;
4+
import org.bukkit.entity.Player;
5+
6+
/**
7+
* Handles I/O for storing LimboPlayer objects.
8+
*/
9+
interface LimboPersistenceHandler {
10+
11+
/**
12+
* Returns the limbo player for the given player if it exists.
13+
*
14+
* @param player the player
15+
* @return the stored limbo player, or null if not available
16+
*/
17+
LimboPlayer getLimboPlayer(Player player);
18+
19+
/**
20+
* Saves the given limbo player for the given player to the disk.
21+
*
22+
* @param player the player to save the limbo player for
23+
* @param limbo the limbo player to save
24+
*/
25+
void saveLimboPlayer(Player player, LimboPlayer limbo);
26+
27+
/**
28+
* Removes the limbo player from the disk.
29+
*
30+
* @param player the player whose limbo player should be removed
31+
*/
32+
void removeLimboPlayer(Player player);
33+
34+
/**
35+
* @return the type of the limbo persistence implementation
36+
*/
37+
LimboPersistenceType getType();
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package fr.xephi.authme.data.limbo.persistence;
2+
3+
/**
4+
* Types of persistence for LimboPlayer objects.
5+
*/
6+
public enum LimboPersistenceType {
7+
8+
INDIVIDUAL_FILES(SeparateFilePersistenceHandler.class),
9+
10+
DISABLED(NoOpPersistenceHandler.class);
11+
12+
private final Class<? extends LimboPersistenceHandler> implementationClass;
13+
14+
/**
15+
* Constructor.
16+
*
17+
* @param implementationClass the implementation class
18+
*/
19+
LimboPersistenceType(Class<? extends LimboPersistenceHandler> implementationClass) {
20+
this.implementationClass= implementationClass;
21+
}
22+
23+
/**
24+
* @return class implementing the persistence type
25+
*/
26+
public Class<? extends LimboPersistenceHandler> getImplementationClass() {
27+
return implementationClass;
28+
}
29+
}

0 commit comments

Comments
 (0)