-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update and debug database logic on player dis/connection
- Loading branch information
Xharos
committed
Jun 26, 2024
1 parent
a820897
commit b9afd14
Showing
7 changed files
with
174 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,17 +8,21 @@ | |
import com.velocitypowered.api.event.connection.PreLoginEvent; | ||
import com.velocitypowered.api.event.player.ServerPreConnectEvent; | ||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; | ||
import com.velocitypowered.api.proxy.Player; | ||
import fr.islandswars.commons.service.collection.Collection; | ||
import fr.islandswars.commons.service.mongodb.MongoDBConnection; | ||
import fr.islandswars.commons.service.mongodb.ObservableSubscriber; | ||
import fr.islandswars.commons.service.mongodb.OperationSubscriber; | ||
import fr.islandswars.commons.service.redis.RedisConnection; | ||
import fr.islandswars.commons.utils.ReflectionUtil; | ||
import fr.islandswars.ineundo.Ineundo; | ||
import fr.islandswars.ineundo.lang.IneundoError; | ||
import fr.islandswars.ineundo.log.internal.PlayerConnectionLog; | ||
import fr.islandswars.ineundo.player.IslandsPlayer; | ||
import fr.islandswars.ineundo.utils.MongoConstants; | ||
import fr.islandswars.ineundo.utils.RedisConstants; | ||
import net.kyori.adventure.text.Component; | ||
import org.apache.logging.log4j.Level; | ||
import org.bson.Document; | ||
|
||
import java.lang.reflect.Field; | ||
|
@@ -27,10 +31,7 @@ | |
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.UUID; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.CompletionStage; | ||
import java.util.concurrent.CopyOnWriteArrayList; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.*; | ||
|
||
/** | ||
* File <b>PlayerDataListener</b> located on fr.islandswars.ineundo.listener | ||
|
@@ -55,11 +56,10 @@ | |
* @author Jangliu, {@literal <[email protected]>} | ||
* Created the 24/06/2024 at 16:10 | ||
* @since 0.1 | ||
* TODO proper logging | ||
*/ | ||
public class PlayerDataListener extends LazyListener { | ||
|
||
private final long mongoTimeout = 2L; | ||
private final long mongoTimeout = 1L; | ||
private final TimeUnit mongoTimeoutUnit = TimeUnit.SECONDS; | ||
private final List<OperationSubscriber<UpdateResult>> pendingResults; | ||
private final Collection<IslandsPlayer> playersCollection; | ||
|
@@ -72,10 +72,13 @@ public PlayerDataListener(Ineundo ineundo, MongoDBConnection mongo, RedisConnect | |
this.playersCollection = mongo.getCollection(MongoConstants.PLAYER_COLLECTION, IslandsPlayer.class); | ||
} | ||
|
||
@Subscribe | ||
@Subscribe(order = PostOrder.FIRST) | ||
public void onLogin(PreLoginEvent event) { | ||
var uuid = event.getUniqueId(); | ||
fetchPlayerData(uuid); | ||
getIneundo().getPlayer(uuid).ifPresentOrElse(p -> { | ||
event.setResult(PreLoginEvent.PreLoginComponentResult.denied(Component.translatable("event.join.data.error"))); | ||
new PlayerConnectionLog(Level.ERROR, "Player not saved in database").withEvent(event).log(); | ||
}, () -> fetchPlayerData(uuid, event)); | ||
} | ||
|
||
@Subscribe(order = PostOrder.FIRST) | ||
|
@@ -86,64 +89,75 @@ public EventTask onServerPreconnectEvent(ServerPreConnectEvent event) { | |
return null; | ||
} | ||
|
||
@Subscribe | ||
@Subscribe(order = PostOrder.LAST) | ||
public void onPlayerDisconnect(DisconnectEvent event) { | ||
var uuid = event.getPlayer().getUniqueId(); | ||
var optPlayer = getPlayer(uuid); | ||
optPlayer.ifPresent(this::savePlayerData); | ||
} | ||
|
||
@Subscribe | ||
@Subscribe(order = PostOrder.FIRST) | ||
public void onProxyShutdown(ProxyShutdownEvent event) { | ||
while (!pendingResults.isEmpty()) { | ||
} | ||
} | ||
|
||
private void savePlayerData(IslandsPlayer player) { | ||
updateFromRedis(player).whenCompleteAsync((p, thr) -> { | ||
if (thr != null) | ||
error(new IneundoError("Error when saving player data in MongoDB...", thr)); | ||
updateFromRedis(player).whenCompleteAsync((p, th) -> { | ||
if (th != null) | ||
error(new IneundoError("Error when retrieving player data from Redis", th)); | ||
|
||
var subscriber = playersCollection.replace(player, MongoConstants.PLAYER_ID_FILTER(player.getUUID())); | ||
var subscriber = playersCollection.replace(p, MongoConstants.PLAYER_ID_FILTER(p.getUUID())); | ||
pendingResults.add(subscriber); | ||
|
||
CompletableFuture<UpdateResult> result = new CompletableFuture<>(); | ||
result.completeAsync(subscriber::first); | ||
result.whenCompleteAsync((re, th) -> { | ||
if (th != null) | ||
error(new IneundoError("Error when saving player data in MongoDB...", th)); | ||
result.completeAsync(subscriber::first).orTimeout(mongoTimeout, mongoTimeoutUnit); | ||
result.whenCompleteAsync((re, thr) -> { | ||
if (thr != null) { | ||
error(new IneundoError("Error when saving player data in MongoDB.", thr)); | ||
getIneundo().getInfraLogger().log(Level.ERROR, playersCollection.serialize(p).toJson()); //manual save in case of problem | ||
} | ||
getIneundo().removePlayer(player); | ||
pendingResults.remove(subscriber); | ||
}); | ||
//TODO maybe add a timeout here in case the player wants to connect back to the server | ||
//TODO or check if a player with this uuid is already existing when joining | ||
}); | ||
} | ||
|
||
private void synchroniseData(ServerPreConnectEvent event) { | ||
getPlayerAsync(event.getPlayer().getUniqueId()).whenCompleteAsync((optPlayer, th) -> { | ||
if (th != null || optPlayer.isEmpty()) { | ||
error(new IneundoError("Player " + event.getPlayer().getUsername() + " cannot be retrieved in time", th)); | ||
//error(new IneundoError("Player " + event.getPlayer().getUsername() + " cannot be retrieved in time", th)); | ||
event.getPlayer().disconnect(Component.translatable("event.join.data.error")); | ||
new PlayerConnectionLog(Level.ERROR, "Cannot retrieve data from mongodb in time").withEvent(event).log(); | ||
} else { | ||
var player = optPlayer.get(); | ||
var player = optPlayer.get(); | ||
injectGameProfile(player, event.getPlayer()); | ||
var sanction = player.isKick(); | ||
sanction.ifPresent(s -> event.getPlayer().disconnect(s.getKickMessage())); | ||
if (getIneundo().getSTAFF_ONLY().get() && !player.getMainRank().isStaff()) event.getPlayer().disconnect(Component.translatable("event.join.staff")); | ||
else { | ||
sanction.ifPresent(s -> { | ||
event.getPlayer().disconnect(s.getKickMessage()); | ||
new PlayerConnectionLog(Level.INFO, "Kicked player attempt to login").withEvent(event).log(); | ||
}); | ||
if (getIneundo().getSTAFF_ONLY().get() && !player.getMainRank().isStaff()) { | ||
event.getPlayer().disconnect(Component.translatable("event.join.staff")); | ||
new PlayerConnectionLog(Level.INFO, "Connection attempt when the server is in maintenance").withEvent(event).log(); | ||
} else { | ||
//TODO server offline | ||
redis.getConnection().set(RedisConstants.PLAYER_KEY(player.getUUID()), playersCollection.serialize(player).toJson()); | ||
redis.getConnection().set(RedisConstants.PLAYER_KEY(player.getUUID()), playersCollection.serialize(player).toJson()).whenCompleteAsync((re, thr) -> { | ||
if (thr != null) { | ||
event.getPlayer().disconnect(Component.translatable("event.join.data.error")); | ||
new PlayerConnectionLog(Level.ERROR, "Cannot save data in redis").withEvent(event).log(); | ||
} else | ||
new PlayerConnectionLog(Level.INFO, "Successful connection").withEvent(event).log(); | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
private CompletionStage<IslandsPlayer> updateFromRedis(IslandsPlayer current) { | ||
return redis.getConnection().get(current.getUUID().toString() + ":player").handleAsync((json, th) -> { | ||
if (th != null) | ||
error(new IneundoError("Cannot retrieve player data on redis...", th)); | ||
|
||
return redis.getConnection().get(RedisConstants.PLAYER_KEY(current.getUUID())).thenApply((json) -> { | ||
if (json != null) { | ||
log(json); | ||
var retrieved = playersCollection.deserialize(Document.parse(json)); | ||
Field[] fields = retrieved.getClass().getDeclaredFields(); | ||
for (Field field : fields) { | ||
|
@@ -160,18 +174,27 @@ private CompletionStage<IslandsPlayer> updateFromRedis(IslandsPlayer current) { | |
}); | ||
} | ||
|
||
private void fetchPlayerData(UUID uuid) { | ||
private void fetchPlayerData(UUID uuid, PreLoginEvent event) { | ||
var publisher = playersCollection.findOne(MongoConstants.PLAYER_ID_FILTER(uuid)); | ||
publisher.thenApplyAsync(player -> { | ||
if (!event.getConnection().getProtocolVersion().isSupported()) { | ||
new PlayerConnectionLog(Level.WARN, "Minecraft version not supported!").withEvent(event).log(); | ||
throw new UnsupportedOperationException("Outdated client version"); | ||
} | ||
PlayerConnectionLog log; | ||
if (player == null) { | ||
player = new IslandsPlayer(); | ||
player.firstConection(uuid); | ||
log = new PlayerConnectionLog(Level.INFO, "First login attempt to join the server"); | ||
} else { | ||
player.welcomeBack(); | ||
log = new PlayerConnectionLog(Level.INFO, "Login attempt to join the server"); | ||
} | ||
log.withEvent(event).log(); | ||
return player; | ||
}).thenAcceptAsync(player -> getIneundo().addPlayer(player)).orTimeout(mongoTimeout, mongoTimeoutUnit).exceptionallyAsync(th -> { | ||
error(new IneundoError("MongoDB timeout when retrieving player data", th)); | ||
if (!(th instanceof UnsupportedOperationException))//check if mongo can throw this error | ||
error(new IneundoError(th)); | ||
return null; | ||
}); | ||
} | ||
|
@@ -192,4 +215,13 @@ private CompletableFuture<Optional<IslandsPlayer>> getPlayerAsync(UUID uuid) { | |
}); | ||
return future; | ||
} | ||
|
||
private void injectGameProfile(IslandsPlayer isPlayer, Player player) { | ||
var profileProperty = player.getGameProfile().getProperties().stream().filter(prop -> prop.getName().equals("textures")).findFirst(); | ||
profileProperty.ifPresent(prop ->{ | ||
if (isPlayer.getProfile() == null || !isPlayer.getProfile().equals(prop)) | ||
isPlayer.setProfile(prop); | ||
}); | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
src/main/java/fr/islandswars/ineundo/log/internal/PlayerConnectionLog.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package fr.islandswars.ineundo.log.internal; | ||
|
||
import com.google.common.base.Preconditions; | ||
import com.google.gson.annotations.SerializedName; | ||
import com.velocitypowered.api.event.connection.PreLoginEvent; | ||
import com.velocitypowered.api.event.player.ServerPreConnectEvent; | ||
import com.velocitypowered.api.network.ProtocolVersion; | ||
import com.velocitypowered.api.proxy.InboundConnection; | ||
import fr.islandswars.ineundo.log.Log; | ||
import org.apache.logging.log4j.Level; | ||
|
||
import java.net.InetSocketAddress; | ||
import java.util.UUID; | ||
|
||
/** | ||
* File <b>PlayerConnectionLog</b> located on fr.islandswars.ineundo.log.internal | ||
* PlayerConnectionLog is a part of ineundo. | ||
* <p> | ||
* Copyright (c) 2017 - 2024 Islands Wars. | ||
* <p> | ||
* ineundo is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* <p> | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* <p> | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <a href="http://www.gnu.org/licenses/">GNU license</a>. | ||
* <p> | ||
* | ||
* @author Jangliu, {@literal <[email protected]>} | ||
* Created the 26/06/2024 at 22:23 | ||
* @since 0.1 | ||
*/ | ||
public class PlayerConnectionLog extends Log { | ||
|
||
private UUID uuid; | ||
private String name; | ||
@SerializedName("protocol_state") | ||
private String protocolState; | ||
@SerializedName("protocol_version") | ||
private ProtocolVersion protocolVersion; | ||
private String address; | ||
|
||
public PlayerConnectionLog(Level level, String msg) { | ||
super(level, msg); | ||
} | ||
|
||
@Override | ||
protected void checkValue() { | ||
Preconditions.checkNotNull(uuid); | ||
Preconditions.checkNotNull(name); | ||
Preconditions.checkNotNull(protocolState); | ||
Preconditions.checkNotNull(protocolVersion); | ||
Preconditions.checkNotNull(address); | ||
} | ||
|
||
public PlayerConnectionLog withEvent(PreLoginEvent event) { | ||
this.protocolState = event.getConnection().getProtocolState().name(); | ||
this.protocolVersion = event.getConnection().getProtocolVersion(); | ||
this.address = event.getConnection().getRemoteAddress().toString(); | ||
this.uuid = event.getUniqueId(); | ||
this.name = event.getUsername(); | ||
return this; | ||
} | ||
|
||
public PlayerConnectionLog withEvent(ServerPreConnectEvent event) { | ||
this.protocolState = event.getPlayer().getProtocolState().name(); | ||
this.protocolVersion = event.getPlayer().getProtocolVersion(); | ||
this.address = event.getPlayer().getRemoteAddress().toString(); | ||
this.uuid = event.getPlayer().getUniqueId(); | ||
this.name = event.getPlayer().getUsername(); | ||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.