Fix fabric join address gathering (#2546)

- Refactored player online listeners to move common. often changed logic to common package
- Changed method fabric uses to get join address to use the client handshake packet which has the address
- Added command `/plan db removejoinaddresses {server}` to allow removing invalid join address data
- Changed build pipeline to always build jars even if tests fail
- Disabled one flaky test

Affects issues:
- Closed #817
- Fixed  #2526
This commit is contained in:
Aurora Lahtela 2022-08-14 20:35:32 +03:00 committed by GitHub
parent d1802ff7ba
commit b646e18c68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 2403 additions and 943 deletions

View File

@ -53,16 +53,10 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build with Gradle
env:
MYSQL_DB: test
MYSQL_USER: user
MYSQL_PASS: password
MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }}
CHROMEDRIVER: /usr/local/bin/chromedriver
- name: Build jars
run: |
cd Plan
./gradlew build
./gradlew shadowJar
- name: Get versions
run: |
cd Plan
@ -82,6 +76,16 @@ jobs:
with:
name: PlanFabric-${{ env.versionString }}-${{ env.git_hash }}.jar
path: ${{ env.artifactPath }}/PlanFabric-${{ env.snapshotVersion }}.jar
- name: Test
env:
MYSQL_DB: test
MYSQL_USER: user
MYSQL_PASS: password
MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }}
CHROMEDRIVER: /usr/local/bin/chromedriver
run: |
cd Plan
./gradlew build
- name: SonarCloud
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,85 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.domain;
import org.bukkit.entity.Player;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.UUID;
public class BukkitPlayerData implements PlatformPlayerData {
private final Player player;
private final String joinAddress;
public BukkitPlayerData(Player player, String joinAddress) {
this.player = player;
this.joinAddress = joinAddress;
}
@Override
public UUID getUUID() {
return player.getUniqueId();
}
@Override
public String getName() {
return player.getName();
}
@Override
public Optional<String> getDisplayName() {
return Optional.of(player.getDisplayName());
}
@Override
public Optional<Boolean> isBanned() {
return Optional.of(player.isBanned());
}
@Override
public Optional<Boolean> isOperator() {
return Optional.of(player.isOp());
}
@Override
public Optional<String> getJoinAddress() {
return Optional.ofNullable(joinAddress);
}
@Override
public Optional<String> getCurrentWorld() {
return Optional.of(player.getWorld().getName());
}
@Override
public Optional<String> getCurrentGameMode() {
return Optional.ofNullable(player.getGameMode()).map(Enum::name);
}
@Override
public Optional<Long> getRegisterDate() {
return Optional.of(player.getFirstPlayed());
}
@Override
public Optional<InetAddress> getIPAddress() {
return Optional.ofNullable(player.getAddress()).map(InetSocketAddress::getAddress);
}
}

View File

@ -16,30 +16,20 @@
*/
package com.djrapitops.plan.gathering.listeners.bukkit;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.delivery.domain.PlayerName;
import com.djrapitops.plan.delivery.domain.ServerName;
import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.gathering.cache.JoinAddressCache;
import com.djrapitops.plan.gathering.domain.BukkitPlayerData;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer;
import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer;
import com.djrapitops.plan.gathering.listeners.Status;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.transactions.events.*;
import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction;
import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@ -49,12 +39,7 @@ import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import javax.inject.Inject;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
/**
* Event Listener for PlayerJoin, PlayerQuit and PlayerKickEvents.
@ -63,63 +48,47 @@ import java.util.function.Supplier;
*/
public class PlayerOnlineListener implements Listener {
private final PlanConfig config;
private final Processing processing;
private final PlayerJoinEventConsumer playerJoinEventConsumer;
private final PlayerLeaveEventConsumer playerLeaveEventConsumer;
private final JoinAddressCache joinAddressCache;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ExtensionSvc extensionService;
private final Exporter exporter;
private final GeolocationCache geolocationCache;
private final NicknameCache nicknameCache;
private final SessionCache sessionCache;
private final ErrorLogger errorLogger;
private final Status status;
private final Map<UUID, String> joinAddresses;
@Inject
public PlayerOnlineListener(
PlanConfig config,
Processing processing,
PlayerJoinEventConsumer playerJoinEventConsumer,
PlayerLeaveEventConsumer playerLeaveEventConsumer,
JoinAddressCache joinAddressCache,
ServerInfo serverInfo,
DBSystem dbSystem,
ExtensionSvc extensionService,
Exporter exporter,
GeolocationCache geolocationCache,
NicknameCache nicknameCache,
SessionCache sessionCache,
Status status,
ErrorLogger errorLogger
) {
this.config = config;
this.processing = processing;
this.playerJoinEventConsumer = playerJoinEventConsumer;
this.playerLeaveEventConsumer = playerLeaveEventConsumer;
this.joinAddressCache = joinAddressCache;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.extensionService = extensionService;
this.exporter = exporter;
this.geolocationCache = geolocationCache;
this.nicknameCache = nicknameCache;
this.sessionCache = sessionCache;
this.status = status;
this.errorLogger = errorLogger;
joinAddresses = new HashMap<>();
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerLogin(PlayerLoginEvent event) {
try {
PlayerLoginEvent.Result result = event.getResult();
UUID playerUUID = event.getPlayer().getUniqueId();
ServerUUID serverUUID = serverInfo.getServerUUID();
boolean banned = result == PlayerLoginEvent.Result.KICK_BANNED;
boolean banned = PlayerLoginEvent.Result.KICK_BANNED == event.getResult();
String joinAddress = event.getHostname();
if (!joinAddress.isEmpty()) {
joinAddress = joinAddress.substring(0, joinAddress.lastIndexOf(':'));
joinAddresses.put(playerUUID, joinAddress);
dbSystem.getDatabase().executeTransaction(new StoreJoinAddressTransaction(joinAddress));
joinAddressCache.put(playerUUID, joinAddress);
}
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> banned));
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, banned));
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(event, event.getResult()).build());
}
@ -160,68 +129,28 @@ public class PlayerOnlineListener implements Listener {
}
private void actOnJoinEvent(PlayerJoinEvent event) {
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
ServerUUID serverUUID = serverInfo.getServerUUID();
long time = System.currentTimeMillis();
UUID playerUUID = event.getPlayer().getUniqueId();
BukkitAFKListener.afkTracker.performedAction(playerUUID, time);
String world = player.getWorld().getName();
String gm = Optional.ofNullable(player.getGameMode()).map(Enum::name).orElse("Unknown");
Database database = dbSystem.getDatabase();
database.executeTransaction(new WorldNameStoreTransaction(serverUUID, world));
InetAddress address = player.getAddress().getAddress();
Supplier<String> getHostName = () -> getHostname(player);
String playerName = player.getName();
String displayName = player.getDisplayName();
database.executeTransaction(new PlayerServerRegisterTransaction(playerUUID,
player::getFirstPlayed, playerName, serverUUID, getHostName))
.thenRunAsync(() -> {
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
if (gatheringGeolocations) {
database.executeTransaction(
new StoreGeoInfoTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
database.executeTransaction(new OperatorStatusTransaction(playerUUID, serverUUID, player.isOp()));
ActiveSession session = new ActiveSession(playerUUID, serverUUID, time, world, gm);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName()));
session.getExtraData().put(JoinAddress.class, new JoinAddress(getHostName.get()));
sessionCache.cacheSession(playerUUID, session)
.map(StoreSessionTransaction::new)
.ifPresent(database::executeTransaction);
database.executeTransaction(new NicknameStoreTransaction(
playerUUID, new Nickname(displayName, time, serverUUID),
(uuid, name) -> nicknameCache.getDisplayName(playerUUID).map(name::equals).orElse(false)
));
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
});
}
private String getHostname(Player player) {
return joinAddresses.get(player.getUniqueId());
playerJoinEventConsumer.onJoinGameServer(PlayerJoin.builder()
.server(serverInfo.getServer())
.player(new BukkitPlayerData(event.getPlayer(), joinAddressCache.getNullableString(playerUUID)))
.time(time)
.build());
}
@EventHandler(priority = EventPriority.NORMAL)
public void beforePlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
String playerName = player.getName();
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
try {
playerLeaveEventConsumer.beforeLeave(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new BukkitPlayerData(event.getPlayer(), null))
.time(System.currentTimeMillis())
.build());
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(event).build());
}
}
@EventHandler(priority = EventPriority.MONITOR)
@ -235,23 +164,13 @@ public class PlayerOnlineListener implements Listener {
private void actOnQuitEvent(PlayerQuitEvent event) {
long time = System.currentTimeMillis();
Player player = event.getPlayer();
String playerName = player.getName();
UUID playerUUID = player.getUniqueId();
ServerUUID serverUUID = serverInfo.getServerUUID();
UUID playerUUID = event.getPlayer().getUniqueId();
BukkitAFKListener.afkTracker.loggedOut(playerUUID, time);
joinAddresses.remove(playerUUID);
nicknameCache.removeDisplayName(playerUUID);
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, player::isBanned));
sessionCache.endSession(playerUUID, time)
.ifPresent(endedSession -> dbSystem.getDatabase().executeTransaction(new StoreSessionTransaction(endedSession)));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
playerLeaveEventConsumer.onLeaveGameServer(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new BukkitPlayerData(event.getPlayer(), null))
.time(System.currentTimeMillis())
.build());
}
}

View File

@ -1,93 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.modules.bukkit;
import com.djrapitops.plan.DataService;
import com.djrapitops.plan.exceptions.MissingPipelineException;
import com.djrapitops.plan.gathering.cache.JoinAddressCache;
import com.djrapitops.plan.gathering.domain.PlayerMetadata;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import com.djrapitops.plan.identification.ServerUUID;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import javax.inject.Singleton;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.UUID;
@Module
public class BukkitEventPipelineModule {
@Provides
@Singleton
@IntoSet
DataService.Pipeline metadata(JoinAddressCache joinAddressCache) {
return service -> service
.registerMapper(UUID.class, Player.class, PlayerMetadata.class,
player -> getPlayerMetadata(player, service, joinAddressCache));
}
private PlayerMetadata getPlayerMetadata(Player player, DataService service, JoinAddressCache joinAddressCache) {
return PlayerMetadata.builder()
.playerName(player.getName())
.displayName(player.getDisplayName())
.world(player.getWorld().getName())
.gameMode(Optional.ofNullable(player.getGameMode()).map(GameMode::name).orElse(null))
.ipAddress(Optional.ofNullable(player.getAddress()).map(InetSocketAddress::getAddress).orElse(null))
.joinAddress(service.pullWithoutId(JoinAddress.class).map(JoinAddress::getAddress).orElse(null))
.build();
}
@Provides
@Singleton
@IntoSet
DataService.Pipeline events() {
return service -> service
.registerDataServiceMapper(UUID.class, PlayerJoinEvent.class, PlayerJoin.class, this::mapToPlayerJoin)
.registerDataServiceMapper(UUID.class, PlayerQuitEvent.class, PlayerLeave.class, this::mapToPlayerLeave);
}
private PlayerJoin mapToPlayerJoin(DataService service, PlayerJoinEvent event) {
UUID playerUUID = event.getPlayer().getUniqueId();
Optional<PlayerMetadata> metadata = service.map(playerUUID, event.getPlayer(), PlayerMetadata.class);
return PlayerJoin.builder()
.playerUUID(playerUUID)
.serverUUID(service.pullWithoutId(ServerUUID.class).orElseThrow(MissingPipelineException::new))
.playerMetadata(metadata.orElseThrow(MissingPipelineException::new))
.time(System.currentTimeMillis())
.build();
}
private PlayerLeave mapToPlayerLeave(DataService service, PlayerQuitEvent event) {
UUID playerUUID = event.getPlayer().getUniqueId();
Optional<PlayerMetadata> metadata = service.map(playerUUID, event.getPlayer(), PlayerMetadata.class);
return PlayerLeave.builder()
.playerUUID(playerUUID)
.serverUUID(service.pullWithoutId(ServerUUID.class).orElseThrow(MissingPipelineException::new))
.playerMetadata(metadata.orElseThrow(MissingPipelineException::new))
.time(System.currentTimeMillis())
.build();
}
}

View File

@ -0,0 +1,78 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.domain;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.Optional;
import java.util.UUID;
public class BungeePlayerData implements PlatformPlayerData {
private final ProxiedPlayer player;
public BungeePlayerData(ProxiedPlayer player) {this.player = player;}
@Override
public UUID getUUID() {
return player.getUniqueId();
}
@Override
public String getName() {
return player.getName();
}
@Override
public Optional<InetAddress> getIPAddress() {
Optional<InetAddress> ip = getIPFromSocketAddress();
if (ip.isPresent()) return ip;
return getIpFromOldMethod();
}
@SuppressWarnings("deprecation") // ProxiedPlayer#getAddress is deprecated
private Optional<InetAddress> getIpFromOldMethod() {
try {
return Optional.ofNullable(player.getAddress()).map(InetSocketAddress::getAddress);
} catch (NoSuchMethodError e) {
return Optional.empty();
}
}
private Optional<InetAddress> getIPFromSocketAddress() {
try {
SocketAddress socketAddress = player.getSocketAddress();
if (socketAddress instanceof InetSocketAddress) {
return Optional.of(((InetSocketAddress) socketAddress).getAddress());
}
// Unix domain socket address requires Java 16 compatibility.
// These connections come from the same physical machine
Class<?> jdk16SocketAddressType = Class.forName("java.net.UnixDomainSocketAddress");
if (jdk16SocketAddressType.isAssignableFrom(socketAddress.getClass())) {
return Optional.of(InetAddress.getLocalHost());
}
} catch (NoSuchMethodError | ClassNotFoundException | UnknownHostException e) {
// Ignored
}
return Optional.empty();
}
}

View File

@ -16,23 +16,13 @@
*/
package com.djrapitops.plan.gathering.listeners.bungee;
import com.djrapitops.plan.delivery.domain.PlayerName;
import com.djrapitops.plan.delivery.domain.ServerName;
import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.gathering.domain.BungeePlayerData;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer;
import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer;
import com.djrapitops.plan.gathering.events.PlayerSwitchServerEventConsumer;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.transactions.events.PlayerRegisterTransaction;
import com.djrapitops.plan.storage.database.transactions.events.StoreGeoInfoTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@ -44,8 +34,6 @@ import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import javax.inject.Inject;
import java.net.InetAddress;
import java.util.UUID;
/**
* Player Join listener for Bungee.
@ -54,34 +42,24 @@ import java.util.UUID;
*/
public class PlayerOnlineListener implements Listener {
private final PlanConfig config;
private final Processing processing;
private final DBSystem dbSystem;
private final ExtensionSvc extensionService;
private final Exporter exporter;
private final GeolocationCache geolocationCache;
private final SessionCache sessionCache;
private final PlayerJoinEventConsumer joinEventConsumer;
private final PlayerLeaveEventConsumer leaveEventConsumer;
private final PlayerSwitchServerEventConsumer switchServerEventConsumer;
private final ServerInfo serverInfo;
private final ErrorLogger errorLogger;
@Inject
public PlayerOnlineListener(
PlanConfig config,
Processing processing,
DBSystem dbSystem,
ExtensionSvc extensionService,
Exporter exporter, GeolocationCache geolocationCache,
SessionCache sessionCache,
PlayerJoinEventConsumer joinEventConsumer,
PlayerLeaveEventConsumer leaveEventConsumer,
PlayerSwitchServerEventConsumer switchServerEventConsumer,
ServerInfo serverInfo,
ErrorLogger errorLogger
) {
this.config = config;
this.processing = processing;
this.dbSystem = dbSystem;
this.extensionService = extensionService;
this.exporter = exporter;
this.geolocationCache = geolocationCache;
this.sessionCache = sessionCache;
this.joinEventConsumer = joinEventConsumer;
this.leaveEventConsumer = leaveEventConsumer;
this.switchServerEventConsumer = switchServerEventConsumer;
this.serverInfo = serverInfo;
this.errorLogger = errorLogger;
}
@ -96,84 +74,48 @@ public class PlayerOnlineListener implements Listener {
}
private void actOnLogin(PostLoginEvent event) {
ProxiedPlayer player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
String playerName = player.getName();
InetAddress address = player.getAddress().getAddress();
long time = System.currentTimeMillis();
ProxiedPlayer player = event.getPlayer();
ActiveSession session = new ActiveSession(playerUUID, serverInfo.getServerUUID(), time, null, null);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName("Proxy Server"));
sessionCache.cacheSession(playerUUID, session);
Database database = dbSystem.getDatabase();
database.executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> time, playerName))
.thenRunAsync(() -> {
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
if (gatheringGeolocations) {
database.executeTransaction(
new StoreGeoInfoTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
});
joinEventConsumer.onJoinProxyServer(PlayerJoin.builder()
.server(serverInfo.getServer())
.player(new BungeePlayerData(player))
.time(time)
.build());
}
@EventHandler(priority = EventPriority.NORMAL)
public void beforeLogout(PlayerDisconnectEvent event) {
ProxiedPlayer player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
String playerName = player.getName();
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onLogout(PlayerDisconnectEvent event) {
try {
actOnLogout(event);
leaveEventConsumer.beforeLeave(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new BungeePlayerData(event.getPlayer()))
.time(System.currentTimeMillis())
.build());
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(event).build());
}
}
private void actOnLogout(PlayerDisconnectEvent event) {
ProxiedPlayer player = event.getPlayer();
String playerName = player.getName();
UUID playerUUID = player.getUniqueId();
sessionCache.endSession(playerUUID, System.currentTimeMillis());
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
@EventHandler(priority = EventPriority.HIGHEST)
public void onLogout(PlayerDisconnectEvent event) {
try {
leaveEventConsumer.onLeaveProxyServer(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new BungeePlayerData(event.getPlayer()))
.time(System.currentTimeMillis())
.build());
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(event).build());
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onServerSwitch(ServerSwitchEvent event) {
try {
actOnServerSwitch(event);
switchServerEventConsumer.onServerSwitch(new BungeePlayerData(event.getPlayer()), System.currentTimeMillis());
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(event).build());
}
}
private void actOnServerSwitch(ServerSwitchEvent event) {
ProxiedPlayer player = event.getPlayer();
String playerName = player.getName();
UUID playerUUID = player.getUniqueId();
long time = System.currentTimeMillis();
// Replaces the current session in the cache.
ActiveSession session = new ActiveSession(playerUUID, serverInfo.getServerUUID(), time, null, null);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName("Proxy Server"));
sessionCache.cacheSession(playerUUID, session);
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
}
}

View File

@ -332,12 +332,24 @@ public class PlanCommand {
.subcommand(clearCommand())
.subcommand(removeCommand())
.subcommand(uninstalledCommand())
.subcommand(removeJoinAddressesCommand())
.requirePermission(Permissions.DATA_BASE)
.description(locale.getString(HelpLang.DB))
.inDepthDescription(locale.getString(DeepHelpLang.DB))
.build();
}
private Subcommand removeJoinAddressesCommand() {
return Subcommand.builder()
.aliases("removejoinaddresses")
.requirePermission(Permissions.DATA_CLEAR)
.requiredArgument(locale.getString(HelpLang.ARG_SERVER), locale.getString(HelpLang.DESC_ARG_SERVER_IDENTIFIER))
.description(locale.getString(HelpLang.JOIN_ADDRESS_REMOVAL))
.onCommand((sender, arguments) -> databaseCommands.onFixFabricJoinAddresses(commandName, sender, arguments))
.onTabComplete(this::serverNames)
.build();
}
private Subcommand backupCommand() {
return Subcommand.builder()
.aliases("backup")

View File

@ -25,6 +25,7 @@ import com.djrapitops.plan.exceptions.database.DBOpException;
import com.djrapitops.plan.identification.Identifiers;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.query.QuerySvc;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DatabaseSettings;
@ -40,6 +41,7 @@ import com.djrapitops.plan.storage.database.transactions.BackupCopyTransaction;
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
import com.djrapitops.plan.storage.database.transactions.commands.RemovePlayerTransaction;
import com.djrapitops.plan.storage.database.transactions.commands.SetServerAsUninstalledTransaction;
import com.djrapitops.plan.storage.database.transactions.patches.BadFabricJoinAddressValuePatch;
import com.djrapitops.plan.storage.file.PlanFiles;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
@ -48,6 +50,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
@ -323,6 +326,56 @@ public class DatabaseCommands {
}
}
public void onFixFabricJoinAddresses(String mainCommand, CMDSender sender, Arguments arguments) {
String identifier = arguments.concatenate(" ");
Optional<ServerUUID> serverUUID = identifiers.getServerUUID(identifier);
if (serverUUID.isEmpty()) {
throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_SERVER_NOT_FOUND, identifier));
}
Database database = dbSystem.getDatabase();
if (sender.supportsChatEvents()) {
sender.buildMessage()
.addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_JOIN_ADDRESS_REMOVAL, identifier, database.getType().getName())).newLine()
.addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM))
.addPart("§2§l[\u2714]").command("/" + mainCommand + " accept").hover(locale.getString(CommandLang.CONFIRM_ACCEPT))
.addPart(" ")
.addPart("§4§l[\u2718]").command("/" + mainCommand + " cancel").hover(locale.getString(CommandLang.CONFIRM_DENY))
.send();
} else {
sender.buildMessage()
.addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_JOIN_ADDRESS_REMOVAL, identifier, database.getType().getName())).newLine()
.addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM)).addPart("§a/" + mainCommand + " accept")
.addPart(" ")
.addPart("§c/" + mainCommand + " cancel")
.send();
}
confirmation.confirm(sender, choice -> {
if (Boolean.TRUE.equals(choice)) {
performJoinAddressRemoval(sender, serverUUID.get(), database);
} else {
sender.send(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_CANCELLED_DATA));
}
});
}
private void performJoinAddressRemoval(CMDSender sender, ServerUUID serverUUID, Database database) {
try {
sender.send(locale.getString(CommandLang.DB_WRITE, database.getType().getName()));
database.executeTransaction(new BadFabricJoinAddressValuePatch(serverUUID))
.thenRunAsync(() -> sender.send(locale.getString(CommandLang.PROGRESS_SUCCESS)))
.exceptionally(error -> {
sender.send(locale.getString(CommandLang.PROGRESS_FAIL, error.getMessage()));
return null;
});
} catch (DBOpException e) {
sender.send(locale.getString(CommandLang.PROGRESS_FAIL, e.getMessage()));
errorLogger.error(e, ErrorContext.builder().related(sender, database.getType().getName()).build());
}
}
public void onRemove(String mainCommand, CMDSender sender, Arguments arguments) {
String identifier = arguments.concatenate(" ");
UUID playerUUID = identifiers.getPlayerUUID(identifier);

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.gathering.cache;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -45,12 +44,11 @@ public class JoinAddressCache {
}
public Optional<JoinAddress> get(UUID playerUUID) {
return Optional.ofNullable(joinAddresses.get(playerUUID))
.map(JoinAddress::new);
return Optional.ofNullable(getNullableString(playerUUID)).map(JoinAddress::new);
}
public void remove(UUID playerUUID, PlayerLeave leave) {
remove(playerUUID);
public String getNullableString(UUID playerUUID) {
return joinAddresses.get(playerUUID);
}
public void remove(UUID playerUUID) {

View File

@ -18,7 +18,6 @@ package com.djrapitops.plan.gathering.cache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -98,10 +97,6 @@ public class SessionCache {
return Optional.of(activeSession.toFinishedSession(time));
}
public Optional<FinishedSession> endSession(UUID playerUUID, PlayerLeave leave) {
return endSession(playerUUID, leave.getTime());
}
public Optional<FinishedSession> endSession(UUID playerUUID, long time) {
return endSession(playerUUID, time, ACTIVE_SESSIONS.get(playerUUID));
}

View File

@ -16,7 +16,6 @@
*/
package com.djrapitops.plan.gathering.domain;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.identification.ServerUUID;
import java.util.Objects;
@ -49,12 +48,6 @@ public class ActiveSession {
lastMovementForAfkCalculation = start;
}
public static ActiveSession fromPlayerJoin(PlayerJoin join) {
return new ActiveSession(join.getPlayerUUID(), join.getServerUUID(), join.getTime(),
join.getPlayerMetadata().getWorld().orElse("Unspecified"),
join.getPlayerMetadata().getGameMode().orElse("Unknown"));
}
public FinishedSession toFinishedSessionFromStillActive() {
updateState();
FinishedSession finishedSession = toFinishedSession(System.currentTimeMillis());

View File

@ -28,10 +28,10 @@ import java.util.Optional;
*/
public class GMTimes extends TimeKeeper {
private static final String SURVIVAL = "SURVIVAL";
private static final String CREATIVE = "CREATIVE";
private static final String ADVENTURE = "ADVENTURE";
private static final String SPECTATOR = "SPECTATOR";
public static final String SURVIVAL = "SURVIVAL";
public static final String CREATIVE = "CREATIVE";
public static final String ADVENTURE = "ADVENTURE";
public static final String SPECTATOR = "SPECTATOR";
public GMTimes(Map<String, Long> times, String lastState, long lastStateChange) {
super(times, lastState, lastStateChange);

View File

@ -0,0 +1,61 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.domain;
import java.net.InetAddress;
import java.util.Optional;
import java.util.UUID;
public interface PlatformPlayerData {
UUID getUUID();
String getName();
default Optional<String> getDisplayName() {
return Optional.empty();
}
default Optional<Boolean> isBanned() {
return Optional.empty();
}
default Optional<Boolean> isOperator() {
return Optional.empty();
}
default Optional<String> getJoinAddress() {
return Optional.empty();
}
default Optional<String> getCurrentWorld() {
return Optional.empty();
}
default Optional<String> getCurrentGameMode() {
return Optional.empty();
}
default Optional<Long> getRegisterDate() {
return Optional.empty();
}
default Optional<InetAddress> getIPAddress() {
return Optional.empty();
}
}

View File

@ -16,23 +16,23 @@
*/
package com.djrapitops.plan.gathering.domain.event;
import com.djrapitops.plan.gathering.domain.PlayerMetadata;
import com.djrapitops.plan.gathering.domain.PlatformPlayerData;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import java.util.UUID;
public class PlayerJoin {
private final UUID playerUUID;
private final ServerUUID serverUUID;
private final PlayerMetadata playerMetadata;
private final Server server;
private final PlatformPlayerData player;
private final long time;
public PlayerJoin(UUID playerUUID, ServerUUID serverUUID, PlayerMetadata playerMetadata, long time) {
this.playerUUID = playerUUID;
this.serverUUID = serverUUID;
this.playerMetadata = playerMetadata;
private PlayerJoin(Server server, PlatformPlayerData player, long time) {
this.server = server;
this.player = player;
this.time = time;
}
@ -41,43 +41,49 @@ public class PlayerJoin {
}
public UUID getPlayerUUID() {
return playerUUID;
return player.getUUID();
}
public String getPlayerName() {
return player.getName();
}
public ServerUUID getServerUUID() {
return serverUUID;
return server.getUuid();
}
public PlayerMetadata getPlayerMetadata() {
return playerMetadata;
public Server getServer() {
return server;
}
public PlatformPlayerData getPlayer() {
return player;
}
public long getTime() {
return time;
}
public String getJoinAddress() {
return player.getJoinAddress().orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
}
public static final class Builder {
private UUID playerUUID;
private ServerUUID serverUUID;
private PlayerMetadata playerMetadata;
private Server server;
private PlatformPlayerData player;
private long time;
private Builder() {}
public static Builder aPlayerJoin() {return new Builder();}
public Builder playerUUID(UUID playerUUID) {
this.playerUUID = playerUUID;
public Builder server(Server server) {
this.server = server;
return this;
}
public Builder serverUUID(ServerUUID serverUUID) {
this.serverUUID = serverUUID;
return this;
}
public Builder playerMetadata(PlayerMetadata playerMetadata) {
this.playerMetadata = playerMetadata;
public Builder player(PlatformPlayerData player) {
this.player = player;
return this;
}
@ -86,6 +92,6 @@ public class PlayerJoin {
return this;
}
public PlayerJoin build() {return new PlayerJoin(playerUUID, serverUUID, playerMetadata, time);}
public PlayerJoin build() {return new PlayerJoin(server, player, time);}
}
}

View File

@ -16,23 +16,22 @@
*/
package com.djrapitops.plan.gathering.domain.event;
import com.djrapitops.plan.gathering.domain.PlayerMetadata;
import com.djrapitops.plan.gathering.domain.PlatformPlayerData;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import java.util.UUID;
public class PlayerLeave {
private final UUID playerUUID;
private final ServerUUID serverUUID;
private final PlayerMetadata playerMetadata;
private final Server server;
private final PlatformPlayerData player;
private final long time;
public PlayerLeave(UUID playerUUID, ServerUUID serverUUID, PlayerMetadata playerMetadata, long time) {
this.playerUUID = playerUUID;
this.serverUUID = serverUUID;
this.playerMetadata = playerMetadata;
private PlayerLeave(Server server, PlatformPlayerData player, long time) {
this.server = server;
this.player = player;
this.time = time;
}
@ -41,15 +40,19 @@ public class PlayerLeave {
}
public UUID getPlayerUUID() {
return playerUUID;
return player.getUUID();
}
public String getPlayerName() {
return player.getName();
}
public ServerUUID getServerUUID() {
return serverUUID;
return server.getUuid();
}
public PlayerMetadata getPlayerMetadata() {
return playerMetadata;
public PlatformPlayerData getPlayer() {
return player;
}
public long getTime() {
@ -57,27 +60,21 @@ public class PlayerLeave {
}
public static final class Builder {
private UUID playerUUID;
private ServerUUID serverUUID;
private PlayerMetadata playerMetadata;
private Server server;
private PlatformPlayerData player;
private long time;
private Builder() {}
public static Builder aPlayerLeave() {return new Builder();}
public Builder playerUUID(UUID playerUUID) {
this.playerUUID = playerUUID;
public Builder player(PlatformPlayerData player) {
this.player = player;
return this;
}
public Builder serverUUID(ServerUUID serverUUID) {
this.serverUUID = serverUUID;
return this;
}
public Builder playerMetadata(PlayerMetadata playerMetadata) {
this.playerMetadata = playerMetadata;
public Builder server(Server server) {
this.server = server;
return this;
}
@ -86,6 +83,6 @@ public class PlayerLeave {
return this;
}
public PlayerLeave build() {return new PlayerLeave(playerUUID, serverUUID, playerMetadata, time);}
public PlayerLeave build() {return new PlayerLeave(server, player, time);}
}
}

View File

@ -0,0 +1,194 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.events;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.delivery.domain.PlayerName;
import com.djrapitops.plan.delivery.domain.ServerName;
import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import com.djrapitops.plan.storage.database.transactions.events.*;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Singleton
public class PlayerJoinEventConsumer {
private final Processing processing;
private final PlanConfig config;
private final DBSystem dbSystem;
private final GeolocationCache geolocationCache;
private final SessionCache sessionCache;
private final NicknameCache nicknameCache;
private final ExtensionSvc extensionService;
private final Exporter exporter;
@Inject
public PlayerJoinEventConsumer(
Processing processing,
PlanConfig config,
DBSystem dbSystem,
GeolocationCache geolocationCache,
SessionCache sessionCache,
NicknameCache nicknameCache,
ExtensionSvc extensionService,
Exporter exporter
) {
this.processing = processing;
this.config = config;
this.dbSystem = dbSystem;
this.geolocationCache = geolocationCache;
this.sessionCache = sessionCache;
this.nicknameCache = nicknameCache;
this.extensionService = extensionService;
this.exporter = exporter;
}
public void onJoinGameServer(PlayerJoin join) {
processing.submitCritical(() -> {
storeWorldInformation(join);
storeGamePlayer(join)
.thenRunAsync(() -> {
storeJoinAddress(join);
cacheActiveSession(join).ifPresent(this::storeInterruptedSession);
storeGeolocation(join);
storeOperatorStatus(join);
storeNickname(join);
updatePlayerDataExtensionValues(join);
updateExport(join);
}, processing.getCriticalExecutor());
});
}
public void onJoinProxyServer(PlayerJoin join) {
processing.submitCritical(() -> storeProxyPlayer(join)
.thenRunAsync(() -> {
cacheActiveSession(join);
storeGeolocation(join);
updatePlayerDataExtensionValues(join);
updateExport(join);
}, processing.getCriticalExecutor())
);
}
private void storeJoinAddress(PlayerJoin join) {
join.getPlayer().getJoinAddress()
.map(StoreJoinAddressTransaction::new)
.ifPresent(dbSystem.getDatabase()::executeTransaction);
}
private void storeGeolocation(PlayerJoin join) {
if (config.isTrue(DataGatheringSettings.GEOLOCATIONS) && geolocationCache.canGeolocate()) {
join.getPlayer().getIPAddress()
.map(ip -> new StoreGeoInfoTransaction(join.getPlayerUUID(), ip, join.getTime(), geolocationCache::getCountry))
.ifPresent(dbSystem.getDatabase()::executeTransaction);
}
}
private void storeWorldInformation(PlayerJoin join) {
ServerUUID serverUUID = join.getServerUUID();
join.getPlayer().getCurrentWorld()
.map(world -> new WorldNameStoreTransaction(serverUUID, world))
.ifPresent(dbSystem.getDatabase()::executeTransaction);
}
private CompletableFuture<?> storeGamePlayer(PlayerJoin join) {
long registerDate = join.getPlayer().getRegisterDate().orElseGet(join::getTime);
String joinAddress = join.getPlayer().getJoinAddress().orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
Transaction transaction = new PlayerServerRegisterTransaction(
join.getPlayerUUID(), registerDate, join.getPlayer().getName(), join.getServerUUID(), joinAddress
);
return dbSystem.getDatabase().executeTransaction(transaction);
}
private CompletableFuture<?> storeProxyPlayer(PlayerJoin join) {
Transaction transaction = new PlayerRegisterTransaction(
join.getPlayerUUID(), join::getTime, join.getPlayer().getName()
);
return dbSystem.getDatabase().executeTransaction(transaction);
}
private void storeOperatorStatus(PlayerJoin join) {
join.getPlayer().isOperator()
.map(opStatus -> new OperatorStatusTransaction(join.getPlayerUUID(), join.getServerUUID(), opStatus))
.ifPresent(dbSystem.getDatabase()::executeTransaction);
}
Optional<FinishedSession> cacheActiveSession(PlayerJoin join) {
ActiveSession session = mapToActiveSession(join);
return sessionCache.cacheSession(join.getPlayerUUID(), session);
}
private void storeInterruptedSession(FinishedSession finishedSession) {
dbSystem.getDatabase().executeTransaction(new StoreSessionTransaction(finishedSession));
}
private ActiveSession mapToActiveSession(PlayerJoin join) {
ActiveSession session = new ActiveSession(join.getPlayerUUID(), join.getServerUUID(), join.getTime(),
join.getPlayer().getCurrentWorld().orElse(null),
join.getPlayer().getCurrentGameMode().orElse(null));
session.getExtraData().put(PlayerName.class, new PlayerName(join.getPlayer().getName()));
session.getExtraData().put(ServerName.class, new ServerName(join.getServer().isProxy() ? join.getServer().getName() : "Proxy Server"));
session.getExtraData().put(JoinAddress.class, new JoinAddress(join.getJoinAddress()));
return session;
}
private void storeNickname(PlayerJoin join) {
join.getPlayer().getDisplayName()
.map(displayName -> new Nickname(displayName, join.getTime(), join.getServerUUID()))
.map(nickname -> new NicknameStoreTransaction(
join.getPlayerUUID(), nickname,
(uuid, name) -> nicknameCache.getDisplayName(join.getPlayerUUID())
.map(name::equals)
.orElse(false)))
.ifPresent(dbSystem.getDatabase()::executeTransaction);
}
private void updatePlayerDataExtensionValues(PlayerJoin join) {
processing.submitNonCritical(() -> extensionService.updatePlayerValues(
join.getPlayerUUID(), join.getPlayerName(), CallEvents.PLAYER_JOIN)
);
}
void updateExport(PlayerJoin join) {
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(join.getPlayerUUID(), join.getPlayerName()));
}
}
}

View File

@ -0,0 +1,113 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.events;
import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.JoinAddressCache;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction;
import com.djrapitops.plan.storage.database.transactions.events.StoreSessionTransaction;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.UUID;
@Singleton
public class PlayerLeaveEventConsumer {
private final Processing processing;
private final PlanConfig config;
private final DBSystem dbSystem;
private final JoinAddressCache joinAddressCache;
private final NicknameCache nicknameCache;
private final SessionCache sessionCache;
private final ExtensionSvc extensionService;
private final Exporter exporter;
@Inject
public PlayerLeaveEventConsumer(Processing processing, PlanConfig config, DBSystem dbSystem, JoinAddressCache joinAddressCache, NicknameCache nicknameCache, SessionCache sessionCache, ExtensionSvc extensionService, Exporter exporter) {
this.processing = processing;
this.config = config;
this.dbSystem = dbSystem;
this.joinAddressCache = joinAddressCache;
this.nicknameCache = nicknameCache;
this.sessionCache = sessionCache;
this.extensionService = extensionService;
this.exporter = exporter;
}
public void beforeLeave(PlayerLeave leave) {
updatePlayerDataExtensionValues(leave);
}
public void onLeaveGameServer(PlayerLeave leave) {
endSession(leave).ifPresent(this::storeFinishedSession);
storeBanStatus(leave);
updateExport(leave);
cleanFromCache(leave);
}
public void onLeaveProxyServer(PlayerLeave leave) {
endSession(leave);
updateExport(leave);
cleanFromCache(leave);
}
private Optional<FinishedSession> endSession(PlayerLeave leave) {
return sessionCache.endSession(leave.getPlayerUUID(), leave.getTime());
}
private void storeFinishedSession(FinishedSession finishedSession) {
dbSystem.getDatabase().executeTransaction(new StoreSessionTransaction(finishedSession));
}
private void storeBanStatus(PlayerLeave leave) {
processing.submitCritical(() -> leave.getPlayer().isBanned()
.map(banStatus -> new BanStatusTransaction(leave.getPlayerUUID(), leave.getServerUUID(), banStatus))
.ifPresent(dbSystem.getDatabase()::executeTransaction));
}
private void updatePlayerDataExtensionValues(PlayerLeave leave) {
processing.submitNonCritical(() -> extensionService.updatePlayerValues(
leave.getPlayerUUID(), leave.getPlayerName(), CallEvents.PLAYER_JOIN)
);
}
private void updateExport(PlayerLeave leave) {
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(leave.getPlayerUUID(), leave.getPlayerName()));
}
}
private void cleanFromCache(PlayerLeave leave) {
UUID playerUUID = leave.getPlayerUUID();
nicknameCache.removeDisplayName(playerUUID);
joinAddressCache.remove(playerUUID);
}
}

View File

@ -0,0 +1,49 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.events;
import com.djrapitops.plan.gathering.domain.PlatformPlayerData;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.identification.ServerInfo;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class PlayerSwitchServerEventConsumer {
private final PlayerJoinEventConsumer joinEventConsumer;
private final ServerInfo serverInfo;
@Inject
public PlayerSwitchServerEventConsumer(PlayerJoinEventConsumer joinEventConsumer, ServerInfo serverInfo) {
this.joinEventConsumer = joinEventConsumer;
this.serverInfo = serverInfo;
}
// TODO introduce an abstract event/interface for leave/join/switch since they share code.
public void onServerSwitch(PlatformPlayerData player, long time) {
PlayerJoin asJoin = PlayerJoin.builder()
.player(player)
.server(serverInfo.getServer())
.time(time)
.build();
joinEventConsumer.cacheActiveSession(asJoin);
joinEventConsumer.updateExport(asJoin);
}
}

View File

@ -18,13 +18,13 @@ package com.djrapitops.plan.gathering.geolocation;
import com.djrapitops.plan.SubSystem;
import com.djrapitops.plan.exceptions.PreparationException;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.lang.PluginLang;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import net.playeranalytics.plugin.scheduling.RunnableFactory;
import net.playeranalytics.plugin.server.PluginLogger;
import javax.inject.Inject;
@ -47,7 +47,7 @@ public class GeolocationCache implements SubSystem {
private final Locale locale;
private final PlanConfig config;
private final PluginLogger logger;
private final RunnableFactory runnableFactory;
private final Processing processing;
private final Cache<String, String> cache;
private final Geolocator geoLite2Geolocator;
@ -60,13 +60,13 @@ public class GeolocationCache implements SubSystem {
PlanConfig config,
GeoLite2Geolocator geoLite2Geolocator,
PluginLogger logger,
RunnableFactory runnableFactory
Processing processing
) {
this.locale = locale;
this.config = config;
this.geoLite2Geolocator = geoLite2Geolocator;
this.logger = logger;
this.runnableFactory = runnableFactory;
this.processing = processing;
this.cache = Caffeine.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
@ -76,10 +76,10 @@ public class GeolocationCache implements SubSystem {
@Override
public void enable() {
if (config.isTrue(DataGatheringSettings.GEOLOCATIONS)) {
runnableFactory.create(() -> {
processing.submitNonCritical(() -> {
if (inUseGeolocator == null) tryToPrepareGeoLite2();
if (inUseGeolocator == null) logger.error("Failed to enable geolocation.");
}).runTaskAsynchronously();
});
} else {
logger.info(locale.getString(PluginLang.ENABLE_NOTIFY_GEOLOCATIONS_DISABLED));
}

View File

@ -1,95 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.modules;
import com.djrapitops.plan.DataService;
import com.djrapitops.plan.gathering.cache.JoinAddressCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.*;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.domain.event.MobKill;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import com.djrapitops.plan.storage.database.transactions.events.StoreSessionTransaction;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import javax.inject.Singleton;
import java.util.UUID;
@Module // NOTE: not yet in use
public class DataPipelineModule {
@Provides
@Singleton
@IntoSet
DataService.Pipeline playerJoinToSession(SessionCache sessionCache) {
return service -> service
.registerMapper(UUID.class, PlayerJoin.class, ActiveSession.class, ActiveSession::fromPlayerJoin)
.registerSink(UUID.class, ActiveSession.class, sessionCache::cacheSession);
}
@Provides
@Singleton
@IntoSet
DataService.Pipeline joinAddress(JoinAddressCache joinAddressCache) {
return service -> service
.registerSink(UUID.class, JoinAddress.class, joinAddressCache::put)
.registerOptionalPullSource(UUID.class, JoinAddress.class, joinAddressCache::get)
.registerSink(UUID.class, PlayerLeave.class, joinAddressCache::remove);
}
@Provides
@Singleton
@IntoSet
DataService.Pipeline duringSession() {
return service -> service
.registerOptionalPullSource(UUID.class, ActiveSession.class, SessionCache::getCachedSession)
.registerOptionalPullSource(UUID.class, WorldTimes.class, uuid ->
service.pull(ActiveSession.class, uuid)
.map(ActiveSession::getExtraData)
.flatMap(extra -> extra.get(WorldTimes.class)))
.registerOptionalPullSource(UUID.class, MobKillCounter.class, uuid ->
service.pull(ActiveSession.class, uuid)
.map(ActiveSession::getExtraData)
.flatMap(extra -> extra.get(MobKillCounter.class)))
.registerOptionalPullSource(UUID.class, DeathCounter.class, uuid ->
service.pull(ActiveSession.class, uuid)
.map(ActiveSession::getExtraData)
.flatMap(extra -> extra.get(DeathCounter.class)))
.registerOptionalPullSource(UUID.class, PlayerKills.class, uuid ->
service.pull(ActiveSession.class, uuid)
.map(ActiveSession::getExtraData)
.flatMap(extra -> extra.get(PlayerKills.class)))
.registerSink(UUID.class, MobKill.class, (uuid, kill) -> service.pull(MobKillCounter.class, uuid).ifPresent(Counter::add))
.registerSink(UUID.class, PlayerKill.class, (uuid, kill) -> {
service.pull(PlayerKills.class, kill.getKiller().getUuid()).ifPresent(playerKills -> playerKills.add(kill));
service.pull(DeathCounter.class, kill.getVictim().getUuid()).ifPresent(Counter::add);
});
}
@Provides
@Singleton
@IntoSet
DataService.Pipeline playerLeaveToSession(SessionCache sessionCache) {
return service -> service
.registerOptionalMapper(UUID.class, PlayerLeave.class, FinishedSession.class, sessionCache::endSession)
.registerDatabaseSink(UUID.class, FinishedSession.class, (playerUUID, session) -> new StoreSessionTransaction(session));
}
}

View File

@ -70,19 +70,19 @@ public class Processing implements SubSystem {
submitNonCritical(runnable);
}
public void submitNonCritical(Runnable runnable) {
public CompletableFuture<Boolean> submitNonCritical(Runnable runnable) {
if (runnable == null || nonCriticalExecutor.isShutdown()) {
return;
return null;
}
CompletableFuture.supplyAsync(() -> {
return CompletableFuture.supplyAsync(() -> {
runnable.run();
return true;
}, nonCriticalExecutor).handle(this::exceptionHandlerNonCritical);
}
public void submitCritical(Runnable runnable) {
if (runnable == null) return;
CompletableFuture.supplyAsync(() -> {
public CompletableFuture<Boolean> submitCritical(Runnable runnable) {
if (runnable == null) return null;
return CompletableFuture.supplyAsync(() -> {
runnable.run();
return true;
}, criticalExecutor).handle(this::exceptionHandlerCritical);
@ -163,11 +163,7 @@ public class Processing implements SubSystem {
logger.info(locale.get().getString(PluginLang.DISABLED_PROCESSING, criticalTasks.size()));
for (Runnable runnable : criticalTasks) {
if (runnable == null) continue;
try {
runnable.run();
} catch (Exception | NoClassDefFoundError | NoSuchMethodError | NoSuchFieldError e) {
errorLogger.warn(e, ErrorContext.builder().build());
}
tryFinishCriticalTask(runnable);
}
}
} catch (InterruptedException e) {
@ -175,6 +171,14 @@ public class Processing implements SubSystem {
}
}
private void tryFinishCriticalTask(Runnable runnable) {
try {
runnable.run();
} catch (Exception | NoClassDefFoundError | NoSuchMethodError | NoSuchFieldError e) {
errorLogger.warn(e, ErrorContext.builder().build());
}
}
private void ensureShutdown() {
try {
if (!nonCriticalExecutor.isTerminated()) {
@ -190,4 +194,8 @@ public class Processing implements SubSystem {
Thread.currentThread().interrupt();
}
}
public Executor getCriticalExecutor() {
return criticalExecutor;
}
}

View File

@ -124,6 +124,7 @@ public enum CommandLang implements Lang {
IMPORTERS("command.database.manage.importers", "Manage - List Importers", "Importers: "),
CONFIRM_OVERWRITE("command.database.manage.confirmOverwrite", "Manage - Confirm Overwrite", "Data in ${0} will be overwritten!"),
CONFIRM_REMOVAL("command.database.manage.confirmRemoval", "Manage - Confirm Removal", "Data in ${0} will be removed!"),
CONFIRM_JOIN_ADDRESS_REMOVAL("command.database.manage.confirmPartialRemoval", "Manage - Confirm Partial Removal", "Join Address Data for Server ${0} in ${1} will be removed!"),
FAIL_SAME_DB("command.database.manage.failSameDB", "Manage - Fail Same Database", "> §cCan not operate on to and from the same database!"),
FAIL_INCORRECT_DB("command.database.manage.failIncorrectDB", "Manage - Fail Incorrect Database", "> §c'${0}' is not a supported database."),
FAIL_FILE_NOT_FOUND("command.database.manage.failFileNotFound", "Manage - Fail File not found", "> §cNo File found at ${0}"),

View File

@ -70,7 +70,8 @@ public enum HelpLang implements Lang {
EXPORT("command.help.export.description", "Command Help - /plan export", "Export html or json files manually"),
IMPORT("command.help.import.description", "Command Help - /plan import", "Import data"),
JSON("command.help.json.description", "Command Help - /plan json", "View json of Player's raw data."),
LOGOUT("command.help.logout.description", "Command Help - /plan logout", "Log out other users from the panel.");
LOGOUT("command.help.logout.description", "Command Help - /plan logout", "Log out other users from the panel."),
JOIN_ADDRESS_REMOVAL("command.help.removejoinaddresses.description", "Command Help - /plan db removejoinaddresses", "Remove join addresses of a specified server");
private final String identifier;
private final String key;

View File

@ -430,4 +430,8 @@ public abstract class SQLDB extends AbstractDatabase {
public boolean shouldDropUnimportantTransactions() {
return dropUnimportantTransactions.get();
}
public int getTransactionQueueSize() {
return transactionQueueSize.get();
}
}

View File

@ -41,6 +41,10 @@ public class BanStatusTransaction extends Transaction {
private final ServerUUID serverUUID;
private final BooleanSupplier banStatus;
public BanStatusTransaction(UUID playerUUID, ServerUUID serverUUID, boolean banStatus) {
this(playerUUID, serverUUID, () -> banStatus);
}
public BanStatusTransaction(UUID playerUUID, ServerUUID serverUUID, BooleanSupplier banStatus) {
this.playerUUID = playerUUID;
this.serverUUID = serverUUID;

View File

@ -43,6 +43,10 @@ public class PlayerServerRegisterTransaction extends PlayerRegisterTransaction {
this.getJoinAddress = getJoinAddress;
}
public PlayerServerRegisterTransaction(UUID playerUUID, long registerDate, String name, ServerUUID serverUUID, String joinAddress) {
this(playerUUID, () -> registerDate, name, serverUUID, () -> joinAddress);
}
@Override
protected void performOperations() {
super.performOperations();

View File

@ -0,0 +1,70 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.storage.database.transactions.patches;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.storage.database.sql.tables.ServerTable;
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable;
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
/**
* Patch that removes player IPs that were gathered as join address on Fabric.
*
* @author AuroraLS3
*/
public class BadFabricJoinAddressValuePatch extends Patch {
private final ServerUUID serverUUID;
public BadFabricJoinAddressValuePatch(ServerUUID serverUUID) {this.serverUUID = serverUUID;}
@Override
public boolean hasBeenApplied() {
return false; // There is no good way to detect this, so this patch has to be applied manually
}
@Override
protected void applyPatch() {
execute(new ExecStatement("UPDATE " + UserInfoTable.TABLE_NAME + " SET " + UserInfoTable.JOIN_ADDRESS + "=?" +
WHERE + UserInfoTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setNull(1, Types.VARCHAR);
statement.setString(2, serverUUID.toString());
}
});
execute(new ExecStatement("UPDATE " + SessionsTable.TABLE_NAME +
" SET " + SessionsTable.JOIN_ADDRESS_ID + "=" + JoinAddressTable.SELECT_ID +
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
statement.setString(2, serverUUID.toString());
}
});
execute("DELETE FROM " + JoinAddressTable.TABLE_NAME +
WHERE + JoinAddressTable.ID + " NOT IN (" + SELECT + DISTINCT + SessionsTable.JOIN_ADDRESS_ID + FROM + SessionsTable.TABLE_NAME + ")");
}
}

View File

@ -88,6 +88,7 @@ class PlanCommandTest {
"db clear SQLite",
"db remove Test",
"db uninstalled 1",
"db removejoinaddresses 1",
})
void commandWithoutPermissionsReturnsPermissionDenied(String command) {
CMDSender sender = runCommand(command);

View File

@ -0,0 +1,83 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.cache;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import utilities.TestConstants;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author AuroraLS3
*/
class JoinAddressCacheTest {
private JoinAddressCache underTest;
@BeforeEach
void setUp() {
underTest = new JoinAddressCache();
}
@Test
void valueIsNotCached() {
assertTrue(underTest.get(TestConstants.PLAYER_ONE_UUID).isEmpty());
}
@Test
void valueIsCached() {
underTest.put(TestConstants.PLAYER_ONE_UUID, "Test");
Optional<JoinAddress> cached = underTest.get(TestConstants.PLAYER_ONE_UUID);
assertTrue(cached.isPresent());
assertEquals("Test", cached.get().getAddress());
}
@Test
void valueIsCachedAsJoinAddress() {
underTest.put(TestConstants.PLAYER_ONE_UUID, new JoinAddress("Test"));
Optional<JoinAddress> cached = underTest.get(TestConstants.PLAYER_ONE_UUID);
assertTrue(cached.isPresent());
assertEquals("Test", cached.get().getAddress());
}
@Test
void valueIsCachedAsString() {
underTest.put(TestConstants.PLAYER_ONE_UUID, new JoinAddress("Test"));
String cached = underTest.getNullableString(TestConstants.PLAYER_ONE_UUID);
assertEquals("Test", cached);
}
@Test
void valueIsNotCachedAsString() {
String cached = underTest.getNullableString(TestConstants.PLAYER_ONE_UUID);
assertNull(cached);
}
@Test
void valueIsRemoved() {
valueIsCached();
underTest.remove(TestConstants.PLAYER_ONE_UUID);
valueIsNotCached();
}
}

View File

@ -0,0 +1,313 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.events;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.BaseUser;
import com.djrapitops.plan.gathering.domain.PlatformPlayerData;
import com.djrapitops.plan.gathering.domain.UserInfo;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.SQLDB;
import com.djrapitops.plan.storage.database.queries.objects.*;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.storage.database.sql.tables.WorldTable;
import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction;
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
import extension.FullSystemExtension;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.shaded.org.awaitility.Awaitility;
import utilities.TestConstants;
import utilities.dagger.PlanPluginComponent;
import utilities.mocks.objects.TestPlayerData;
import java.io.File;
import java.net.InetAddress;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.djrapitops.plan.storage.database.sql.building.Sql.FROM;
import static com.djrapitops.plan.storage.database.sql.building.Sql.SELECT;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(FullSystemExtension.class)
class PlayerJoinEventConsumerTest {
private static Server server;
private static PlayerJoinEventConsumer underTest;
@BeforeAll
static void beforeAll(PlanConfig config, PlanSystem system, PlanPluginComponent component) {
config.set(DataGatheringSettings.GEOLOCATIONS, true);
config.set(DataGatheringSettings.ACCEPT_GEOLITE2_EULA, true);
system.enable();
server = system.getServerInfo().getServer();
underTest = component.joinConsumer();
}
@AfterAll
static void afterAll(PlanSystem system, Database database) throws ExecutionException, InterruptedException {
database.executeTransaction(new RemoveEverythingTransaction()).get();
system.disable();
SessionCache.clear();
}
@BeforeEach
void resetSystem(PlanConfig config, Database database, ServerUUID serverUUID) {
SessionCache.clear();
config.set(ExportSettings.PLAYER_PAGES, false);
config.set(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE, false);
database.executeTransaction(new RemoveEverythingTransaction());
database.executeTransaction(new StoreServerInformationTransaction(new Server(serverUUID, TestConstants.SERVER_NAME, "", TestConstants.VERSION)));
}
PlayerJoin createPlayerJoin(PlatformPlayerData player) {
return PlayerJoin.builder()
.time(System.currentTimeMillis())
.server(server)
.player(player)
.build();
}
private TestPlayerData createTestPlayer() {
return new TestPlayerData(TestConstants.PLAYER_ONE_UUID, TestConstants.PLAYER_ONE_NAME);
}
@Test
void joiningGameServerStartsSession() {
PlayerJoin join = createPlayerJoin(createTestPlayer()
.setCurrentWorld("World")
.setCurrentGameMode("SURVIVAL"));
underTest.onJoinGameServer(join);
Awaitility.await()
.atMost(1, TimeUnit.SECONDS)
.until(() -> SessionCache.getCachedSession(TestConstants.PLAYER_ONE_UUID).isPresent());
Optional<ActiveSession> cachedSession = SessionCache.getCachedSession(TestConstants.PLAYER_ONE_UUID);
assertTrue(cachedSession.isPresent());
}
@Test
void joiningProxyServerStartsSession() {
PlayerJoin join = createPlayerJoin(createTestPlayer());
underTest.onJoinProxyServer(join);
Awaitility.await()
.atMost(1, TimeUnit.SECONDS)
.until(() -> SessionCache.getCachedSession(TestConstants.PLAYER_ONE_UUID).isPresent());
Optional<ActiveSession> cachedSession = SessionCache.getCachedSession(TestConstants.PLAYER_ONE_UUID);
assertTrue(cachedSession.isPresent());
}
@Test
void joiningGameServerStoresWorldName(Database database) {
PlayerJoin join = createPlayerJoin(createTestPlayer()
.setCurrentWorld("World")
.setCurrentGameMode("SURVIVAL"));
underTest.onJoinGameServer(join);
waitUntilDatabaseIsDone(database);
List<String> expected = List.of("World");
List<String> result = database.queryList(SELECT + WorldTable.NAME + FROM + WorldTable.TABLE_NAME,
set -> set.getString(WorldTable.NAME));
assertEquals(expected, result);
}
@Test
void joiningGameServerStoresJoinAddress(Database database) {
PlayerJoin join = createPlayerJoin(createTestPlayer()
.setJoinAddress("play.testjoinaddress.com"));
underTest.onJoinGameServer(join);
waitUntilDatabaseIsDone(database);
List<String> expected = List.of("play.testjoinaddress.com", JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
List<String> result = database.query(JoinAddressQueries.allJoinAddresses());
assertEquals(expected, result);
}
@Test
void joiningGameServerStoresUser(Database database) {
PlayerJoin join = createPlayerJoin(createTestPlayer()
.setRegisterDate(1234L));
underTest.onJoinGameServer(join);
waitUntilDatabaseIsDone(database);
Collection<BaseUser> expected = List.of(new BaseUser(TestConstants.PLAYER_ONE_UUID, TestConstants.PLAYER_ONE_NAME, 1234L, 0));
Collection<BaseUser> result = database.query(BaseUserQueries.fetchAllBaseUsers());
assertEquals(expected, result);
}
@Test
void joiningProxyServerStoresUser(Database database) {
PlayerJoin join = createPlayerJoin(createTestPlayer());
underTest.onJoinProxyServer(join);
waitUntilDatabaseIsDone(database);
Collection<BaseUser> expected = List.of(new BaseUser(TestConstants.PLAYER_ONE_UUID, TestConstants.PLAYER_ONE_NAME, join.getTime(), 0));
Collection<BaseUser> result = database.query(BaseUserQueries.fetchAllBaseUsers());
assertEquals(expected, result);
}
private void waitUntilDatabaseIsDone(Database database) {
Awaitility.await()
.atMost(2, TimeUnit.SECONDS)
.until(() -> ((SQLDB) database).getTransactionQueueSize() < 1);
}
@Test
void joiningGameServerStoresUserInfo(Database database, ServerUUID serverUUID) {
PlayerJoin join = createPlayerJoin(createTestPlayer()
.setRegisterDate(1234L)
.setJoinAddress("play.testjoinaddress.com"));
underTest.onJoinGameServer(join);
waitUntilDatabaseIsDone(database);
Set<UserInfo> expected = Set.of(new UserInfo(TestConstants.PLAYER_ONE_UUID, serverUUID, 1234L, false, "play.testjoinaddress.com", false));
Set<UserInfo> result = database.query(UserInfoQueries.fetchUserInformationOfUser(TestConstants.PLAYER_ONE_UUID));
assertEquals(expected, result);
}
@Test
void joiningGameServerStoresNickname(Database database) {
PlayerJoin join = createPlayerJoin(createTestPlayer()
.setDisplayName("Nickname"));
underTest.onJoinGameServer(join);
waitUntilDatabaseIsDone(database);
List<String> expected = List.of("Nickname");
List<String> result = database.query(NicknameQueries.fetchNicknameDataOfPlayer(TestConstants.PLAYER_ONE_UUID))
.stream().map(Nickname::getName)
.collect(Collectors.toList());
assertEquals(expected, result);
}
@Test
void joiningGameServerStoresGeolocation(PlanSystem system, Database database) throws Exception {
GeolocationCache geolocationCache = system.getCacheSystem().getGeolocationCache();
Awaitility.await()
.atMost(5, TimeUnit.SECONDS)
.until(geolocationCache::canGeolocate);
PlayerJoin join = createPlayerJoin(createTestPlayer()
.setIp(InetAddress.getByName("156.53.159.86")));
underTest.onJoinGameServer(join);
waitUntilDatabaseIsDone(database);
List<String> expected = List.of("United States");
List<String> result = database.query(GeoInfoQueries.uniqueGeolocations());
assertEquals(expected, result);
}
@Test
void joiningProxyServerStoresGeolocation(PlanSystem system, Database database) throws Exception {
GeolocationCache geolocationCache = system.getCacheSystem().getGeolocationCache();
Awaitility.await()
.atMost(5, TimeUnit.SECONDS)
.until(geolocationCache::canGeolocate);
PlayerJoin join = createPlayerJoin(createTestPlayer()
.setIp(InetAddress.getByName("156.53.159.86")));
underTest.onJoinProxyServer(join);
waitUntilDatabaseIsDone(database);
List<String> expected = List.of("United States");
List<String> result = database.query(GeoInfoQueries.uniqueGeolocations());
assertEquals(expected, result);
}
@Test
void joiningGameServerStoresOperatorStatus(Database database) {
PlayerJoin join = createPlayerJoin(createTestPlayer()
.setOperator(true));
underTest.onJoinGameServer(join);
waitUntilDatabaseIsDone(database);
Set<Integer> result = database.query(UserInfoQueries.userIdsOfNonOperators());
assertTrue(result.isEmpty());
result = database.query(UserInfoQueries.userIdsOfOperators());
assertEquals(1, result.size());
}
@Test
void joiningGameServerExportsPlayerPage(PlanConfig config) {
config.set(ExportSettings.PLAYER_PAGES, true);
config.set(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE, true);
PlayerJoin join = createPlayerJoin(createTestPlayer());
underTest.onJoinGameServer(join);
File playerExportDir = config.getPageExportPath().resolve("player/" + TestConstants.PLAYER_ONE_UUID).toFile();
Awaitility.await()
.atMost(2, TimeUnit.SECONDS)
.until(playerExportDir::exists);
assertTrue(playerExportDir.exists());
assertTrue(playerExportDir.isDirectory());
}
@Test
void joiningProxyServerExportsPlayerPage(PlanConfig config) {
config.set(ExportSettings.PLAYER_PAGES, true);
config.set(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE, true);
PlayerJoin join = createPlayerJoin(createTestPlayer());
underTest.onJoinProxyServer(join);
File playerExportDir = config.getPageExportPath().resolve("player/" + TestConstants.PLAYER_ONE_UUID).toFile();
Awaitility.await()
.atMost(2, TimeUnit.SECONDS)
.until(playerExportDir::exists);
assertTrue(playerExportDir.exists());
assertTrue(playerExportDir.isDirectory());
}
}

View File

@ -0,0 +1,238 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.events;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.ExtensionService;
import com.djrapitops.plan.extension.annotation.NumberProvider;
import com.djrapitops.plan.extension.annotation.PluginInfo;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.*;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.SQLDB;
import com.djrapitops.plan.storage.database.queries.objects.SessionQueries;
import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction;
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
import com.djrapitops.plan.storage.database.transactions.events.PlayerServerRegisterTransaction;
import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction;
import extension.FullSystemExtension;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.shaded.org.awaitility.Awaitility;
import utilities.TestConstants;
import utilities.dagger.PlanPluginComponent;
import utilities.mocks.objects.TestPlayerData;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(FullSystemExtension.class)
class PlayerLeaveEventConsumerTest {
private static Server server;
private static PlayerLeaveEventConsumer underTest;
@BeforeAll
static void beforeAll(PlanConfig config, PlanSystem system, PlanPluginComponent component) {
config.set(DataGatheringSettings.GEOLOCATIONS, true);
config.set(DataGatheringSettings.ACCEPT_GEOLITE2_EULA, true);
system.enable();
server = system.getServerInfo().getServer();
underTest = component.leaveConsumer();
}
@AfterAll
static void afterAll(PlanSystem system, Database database) throws ExecutionException, InterruptedException {
database.executeTransaction(new RemoveEverythingTransaction()).get();
system.disable();
SessionCache.clear();
}
@BeforeEach
void resetSystem(PlanConfig config, Database database, ServerUUID serverUUID) {
SessionCache.clear();
config.set(ExportSettings.PLAYER_PAGES, false);
config.set(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE, false);
database.executeTransaction(new RemoveEverythingTransaction());
database.executeTransaction(new StoreServerInformationTransaction(new Server(serverUUID, TestConstants.SERVER_NAME, "", TestConstants.VERSION)));
}
private void waitUntilDatabaseIsDone(Database database) {
Awaitility.await()
.atMost(2, TimeUnit.SECONDS)
.until(() -> ((SQLDB) database).getTransactionQueueSize() < 1);
}
PlayerLeave createPlayerLeave(PlatformPlayerData player) {
return PlayerLeave.builder()
.time(System.currentTimeMillis())
.server(server)
.player(player)
.build();
}
private TestPlayerData createTestPlayer() {
return new TestPlayerData(TestConstants.PLAYER_ONE_UUID, TestConstants.PLAYER_ONE_NAME);
}
@Test
void leavingGameServerSavesSession(PlanSystem system, Database database, ServerUUID serverUUID) {
SessionCache sessionCache = system.getCacheSystem().getSessionCache();
long sessionStart = System.currentTimeMillis();
sessionCache.cacheSession(TestConstants.PLAYER_ONE_UUID, new ActiveSession(TestConstants.PLAYER_ONE_UUID, serverUUID, sessionStart, "World", GMTimes.SURVIVAL));
database.executeTransaction(new WorldNameStoreTransaction(serverUUID, "World"));
PlayerLeave leave = createPlayerLeave(createTestPlayer());
underTest.onLeaveGameServer(leave);
waitUntilDatabaseIsDone(database);
DataMap extraData = new DataMap();
GMTimes gmTimes = new GMTimes(Map.of(
GMTimes.SURVIVAL, leave.getTime() - sessionStart,
GMTimes.CREATIVE, 0L,
GMTimes.SPECTATOR, 0L,
GMTimes.ADVENTURE, 0L
));
extraData.put(WorldTimes.class, new WorldTimes(Map.of("World", gmTimes)));
extraData.put(PlayerKills.class, new PlayerKills());
extraData.put(MobKillCounter.class, new MobKillCounter());
extraData.put(DeathCounter.class, new DeathCounter());
extraData.put(JoinAddress.class, new JoinAddress(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP));
List<FinishedSession> expected = List.of(new FinishedSession(
TestConstants.PLAYER_ONE_UUID,
serverUUID,
sessionStart,
leave.getTime(),
0L,
extraData
));
List<FinishedSession> result = database.query(SessionQueries.fetchAllSessions());
assertEquals(expected, result);
}
@Test
void leavingGameServerSavesBanStatus(Database database, ServerUUID serverUUID) {
registerPlayer(database, serverUUID);
PlayerLeave leave = createPlayerLeave(createTestPlayer()
.setBanned(true));
underTest.onLeaveGameServer(leave);
waitUntilDatabaseIsDone(database);
Set<Integer> result = database.query(UserInfoQueries.userIdsOfBanned());
assertEquals(1, result.size());
result = database.query(UserInfoQueries.userIdsOfNotBanned());
assertEquals(0, result.size());
}
@Test
void leavingGameServerExportsPlayerPage(PlanConfig config, Database database, ServerUUID serverUUID) {
registerPlayer(database, serverUUID);
config.set(ExportSettings.PLAYER_PAGES, true);
config.set(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE, true);
PlayerLeave leave = createPlayerLeave(createTestPlayer());
underTest.onLeaveGameServer(leave);
File playerExportDir = config.getPageExportPath().resolve("player/" + TestConstants.PLAYER_ONE_UUID).toFile();
Awaitility.await()
.atMost(2, TimeUnit.SECONDS)
.until(playerExportDir::exists);
assertTrue(playerExportDir.exists());
assertTrue(playerExportDir.isDirectory());
}
@Test
void leavingProxyServerExportsPlayerPage(PlanConfig config, Database database, ServerUUID serverUUID) {
registerPlayer(database, serverUUID);
config.set(ExportSettings.PLAYER_PAGES, true);
config.set(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE, true);
PlayerLeave leave = createPlayerLeave(createTestPlayer());
underTest.onLeaveProxyServer(leave);
File playerExportDir = config.getPageExportPath().resolve("player/" + TestConstants.PLAYER_ONE_UUID).toFile();
Awaitility.await()
.atMost(2, TimeUnit.SECONDS)
.until(playerExportDir::exists);
assertTrue(playerExportDir.exists());
assertTrue(playerExportDir.isDirectory());
}
private void registerPlayer(Database database, ServerUUID serverUUID) {
database.executeTransaction(new PlayerServerRegisterTransaction(TestConstants.PLAYER_ONE_UUID, System::currentTimeMillis, TestConstants.PLAYER_ONE_NAME, serverUUID, () -> null))
.join(); // Wait until complete
}
@Test
void extensionDataIsUpdatedBeforeLeave() {
AtomicBoolean called = new AtomicBoolean(false);
@PluginInfo(name = "Extension")
class Extension implements DataExtension {
@NumberProvider(text = "Value")
public long value(UUID playerUUID) {
called.set(true);
return 0L;
}
}
Extension extension = new Extension();
try {
ExtensionService.getInstance().register(extension);
underTest.beforeLeave(createPlayerLeave(createTestPlayer()));
Awaitility.await()
.atMost(2, TimeUnit.SECONDS)
.until(called::get);
assertTrue(called.get());
} finally {
ExtensionService.getInstance().unregister(extension);
}
}
}

View File

@ -16,10 +16,12 @@
*/
package com.djrapitops.plan.gathering.geolocation;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.storage.file.PlanFiles;
import net.playeranalytics.plugin.server.PluginLogger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
@ -28,8 +30,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import utilities.TestErrorLogger;
import utilities.TestPluginLogger;
import utilities.mocks.objects.TestRunnableFactory;
import utilities.mocks.TestProcessing;
import java.io.File;
import java.io.IOException;
@ -86,7 +89,10 @@ class GeolocationTest {
assertTrue(config.isTrue(DataGatheringSettings.GEOLOCATIONS));
GeoLite2Geolocator geoLite2Geolocator = new GeoLite2Geolocator(files, config);
underTest = new GeolocationCache(new Locale(), config, geoLite2Geolocator, new TestPluginLogger(), TestRunnableFactory.forSameThread());
PluginLogger logger = new TestPluginLogger();
Processing processing = new TestProcessing(Locale::new, logger, new TestErrorLogger());
underTest = new GeolocationCache(new Locale(), config, geoLite2Geolocator, logger, processing);
underTest.enable();
assertTrue(underTest.canGeolocate());

View File

@ -21,10 +21,9 @@ import com.djrapitops.plan.delivery.domain.TablePlayer;
import com.djrapitops.plan.delivery.domain.container.PlayerContainer;
import com.djrapitops.plan.delivery.domain.keys.Key;
import com.djrapitops.plan.delivery.domain.keys.PlayerKeys;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.BaseUser;
import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.gathering.domain.GeoInfo;
import com.djrapitops.plan.gathering.domain.*;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.query.QuerySvc;
import com.djrapitops.plan.settings.config.Config;
@ -40,15 +39,19 @@ import com.djrapitops.plan.storage.database.queries.objects.*;
import com.djrapitops.plan.storage.database.queries.objects.playertable.NetworkTablePlayersQuery;
import com.djrapitops.plan.storage.database.queries.objects.playertable.ServerTablePlayersQuery;
import com.djrapitops.plan.storage.database.sql.building.Sql;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable;
import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
import com.djrapitops.plan.storage.database.transactions.StoreConfigTransaction;
import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import com.djrapitops.plan.storage.database.transactions.commands.RemovePlayerTransaction;
import com.djrapitops.plan.storage.database.transactions.events.*;
import com.djrapitops.plan.storage.database.transactions.init.CreateIndexTransaction;
import com.djrapitops.plan.storage.database.transactions.patches.BadFabricJoinAddressValuePatch;
import com.djrapitops.plan.storage.database.transactions.patches.RegisterDateMinimizationPatch;
import com.djrapitops.plan.storage.upkeep.DBCleanTask;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import utilities.FieldFetcher;
import utilities.RandomData;
@ -60,7 +63,9 @@ import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
import static org.junit.jupiter.api.Assertions.*;
@ -348,4 +353,68 @@ public interface DatabaseTest extends DatabaseTestPreparer {
List<TablePlayer> result = db().query(new NetworkTablePlayersQuery(System.currentTimeMillis(), 10L, 1));
assertEquals(1, result.size(), () -> "Incorrect query result: " + result);
}
@Test
@DisplayName("BadFabricJoinAddressValuePatch removes join addresses of one server from sessions")
default void badFabricJoinAddressPatchRemovesJoinAddressesOfOneServer() throws ExecutionException, InterruptedException {
ServerUUID randomSecondServer = ServerUUID.randomUUID();
db().executeTransaction(new StoreServerInformationTransaction(new Server(randomSecondServer, "", "", ""))).get();
db().executeTransaction(new WorldNameStoreTransaction(randomSecondServer, "World"));
db().executeTransaction(new WorldNameStoreTransaction(serverUUID(), "World"));
DataMap extraData1 = new DataMap();
extraData1.put(JoinAddress.class, new JoinAddress("test1"));
extraData1.put(WorldTimes.class, new WorldTimes("World", GMTimes.magicNumberToGMName(0), System.currentTimeMillis()));
FinishedSession session1 = new FinishedSession(playerUUID, serverUUID(), System.currentTimeMillis(), System.currentTimeMillis(), 0L, extraData1);
db().executeTransaction(new StoreSessionTransaction(session1)).get();
DataMap extraData2 = new DataMap();
extraData2.put(JoinAddress.class, new JoinAddress("test2"));
extraData2.put(WorldTimes.class, new WorldTimes("World", GMTimes.magicNumberToGMName(0), System.currentTimeMillis()));
FinishedSession session2 = new FinishedSession(playerUUID, randomSecondServer, System.currentTimeMillis(), System.currentTimeMillis(), 0L, extraData2);
db().executeTransaction(new StoreSessionTransaction(session2)).get();
assertEquals(2, db().query(SessionQueries.fetchAllSessions()).size());
db().executeTransaction(new BadFabricJoinAddressValuePatch(randomSecondServer)).get();
List<String> expected = new ArrayList<>(List.of("test1", JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP));
List<FinishedSession> sessions = db().query(SessionQueries.fetchAllSessions());
System.out.println(sessions);
List<String> result = sessions.stream()
.map(session -> session.getExtraData(JoinAddress.class))
.filter(Optional::isPresent)
.map(Optional::get)
.map(JoinAddress::getAddress)
.collect(Collectors.toList());
Collections.sort(expected);
Collections.sort(result);
assertEquals(expected, result);
result = db().query(JoinAddressQueries.allJoinAddresses());
Collections.sort(result);
assertEquals(expected, result);
}
@Test
@DisplayName("BadFabricJoinAddressValuePatch removes join addresses of one server from plan_user_info")
default void badFabricJoinAddressPatchRemovesJoinAddressesOfOneServerUserInfo() {
ServerUUID randomSecondServer = ServerUUID.randomUUID();
db().executeTransaction(new StoreServerInformationTransaction(new Server(randomSecondServer, "", "", "")));
long time = System.currentTimeMillis();
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> time, "", serverUUID(), () -> "test1"));
db().executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> time, "", randomSecondServer, () -> "test2"));
db().executeTransaction(new BadFabricJoinAddressValuePatch(randomSecondServer));
Set<UserInfo> expected = Set.of(
new UserInfo(playerUUID, serverUUID(), time, false, "test1", false),
new UserInfo(playerUUID, randomSecondServer, time, false, null, false)
);
Set<UserInfo> result = db().query(UserInfoQueries.fetchUserInformationOfUser(playerUUID));
assertEquals(expected, result);
}
}

View File

@ -41,6 +41,7 @@ import com.djrapitops.plan.storage.database.transactions.events.PlayerRegisterTr
import com.djrapitops.plan.storage.database.transactions.events.StoreSessionTransaction;
import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import utilities.OptionalAssert;
import utilities.RandomData;
@ -361,6 +362,7 @@ public interface ExtensionsDatabaseTest extends DatabaseTestPreparer {
}
@Test
@Disabled("Flaky test, possibly due to some kind of concurrent execution - one extra exception is sometimes caught")
default void extensionExceptionsAreCaught() {
TestErrorLogger.throwErrors(false);
ExtensionSvc extensionService = extensionService();

View File

@ -0,0 +1,110 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package extension;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
import com.djrapitops.plan.storage.database.Database;
import org.junit.jupiter.api.extension.*;
import utilities.RandomData;
import utilities.dagger.PlanPluginComponent;
import utilities.mocks.PluginMockComponent;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* JUnit 5 extension to construct a full PlanSystem for a test.
*
* @author AuroraLS3
*/
public class FullSystemExtension implements ParameterResolver, BeforeAllCallback, AfterAllCallback {
private static final int TEST_PORT_NUMBER = RandomData.randomInt(9005, 9500);
public PluginMockComponent component;
private Path tempDir;
private PlanSystem planSystem;
@Override
public void beforeAll(ExtensionContext context) throws Exception {
tempDir = Files.createTempDirectory("plan-fullsystem-test");
component = new PluginMockComponent(tempDir);
planSystem = component.getPlanSystem();
planSystem.getConfigSystem().getConfig()
.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
}
@Override
public void afterAll(ExtensionContext context) throws Exception {
deleteDirectory(tempDir);
}
private void deleteDirectory(Path directory) throws IOException {
Files.list(directory)
.forEach(file -> {
try {
if (Files.isDirectory(file)) {
deleteDirectory(file);
} else {
Files.delete(file);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
Files.delete(directory);
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
Class<?> type = parameterContext.getParameter().getType();
return PlanSystem.class.equals(type) ||
PlanConfig.class.equals(type) ||
ServerUUID.class.equals(type) ||
PlanPluginComponent.class.equals(type) ||
Database.class.equals(type);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
Class<?> type = parameterContext.getParameter().getType();
if (PlanSystem.class.equals(type)) {
return planSystem;
}
if (PlanConfig.class.equals(type)) {
return planSystem.getConfigSystem().getConfig();
}
if (ServerUUID.class.equals(type)) {
return planSystem.getServerInfo().getServerUUID();
}
if (PlanPluginComponent.class.equals(type)) {
try {
return component.getComponent();
} catch (Exception e) {
throw new ParameterResolutionException("Error getting " + type.getName(), e);
}
}
if (Database.class.equals(type)) {
return planSystem.getDatabaseSystem().getDatabase();
}
throw new ParameterResolutionException("Unsupported parameter type " + type.getName());
}
}

View File

@ -19,6 +19,9 @@ package utilities.dagger;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.commands.PlanCommand;
import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer;
import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer;
import com.djrapitops.plan.gathering.events.PlayerSwitchServerEventConsumer;
import com.djrapitops.plan.modules.FiltersModule;
import com.djrapitops.plan.modules.PlaceholderModule;
import com.djrapitops.plan.modules.PlatformAbstractionLayerModule;
@ -59,6 +62,12 @@ public interface PlanPluginComponent {
PlanPlaceholders placeholders();
PlayerJoinEventConsumer joinConsumer();
PlayerLeaveEventConsumer leaveConsumer();
PlayerSwitchServerEventConsumer serverSwitchConsumer();
@Component.Builder
interface Builder {
@BindsInstance

View File

@ -0,0 +1,132 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package utilities.mocks.objects;
import com.djrapitops.plan.gathering.domain.PlatformPlayerData;
import java.net.InetAddress;
import java.util.Optional;
import java.util.UUID;
public class TestPlayerData implements PlatformPlayerData {
private final UUID uuid;
private final String name;
private String displayName;
private Boolean banned;
private Boolean operator;
private String joinAddress;
private String currentWorld;
private String currentGameMode;
private Long registerDate;
private InetAddress ip;
public TestPlayerData(UUID uuid, String name) {
this.uuid = uuid;
this.name = name;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getName() {
return name;
}
@Override
public Optional<String> getDisplayName() {
return Optional.ofNullable(displayName);
}
public TestPlayerData setDisplayName(String displayName) {
this.displayName = displayName;
return this;
}
@Override
public Optional<Boolean> isBanned() {
return Optional.ofNullable(banned);
}
@Override
public Optional<Boolean> isOperator() {
return Optional.ofNullable(operator);
}
@Override
public Optional<String> getJoinAddress() {
return Optional.ofNullable(joinAddress);
}
public TestPlayerData setJoinAddress(String joinAddress) {
this.joinAddress = joinAddress;
return this;
}
@Override
public Optional<String> getCurrentWorld() {
return Optional.ofNullable(currentWorld);
}
public TestPlayerData setCurrentWorld(String currentWorld) {
this.currentWorld = currentWorld;
return this;
}
@Override
public Optional<String> getCurrentGameMode() {
return Optional.ofNullable(currentGameMode);
}
public TestPlayerData setCurrentGameMode(String currentGameMode) {
this.currentGameMode = currentGameMode;
return this;
}
@Override
public Optional<Long> getRegisterDate() {
return Optional.ofNullable(registerDate);
}
public TestPlayerData setRegisterDate(Long registerDate) {
this.registerDate = registerDate;
return this;
}
@Override
public Optional<InetAddress> getIPAddress() {
return Optional.ofNullable(ip);
}
public TestPlayerData setBanned(Boolean banned) {
this.banned = banned;
return this;
}
public TestPlayerData setOperator(Boolean operator) {
this.operator = operator;
return this;
}
public TestPlayerData setIp(InetAddress ip) {
this.ip = ip;
return this;
}
}

View File

@ -0,0 +1,93 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.domain;
import com.djrapitops.plan.gathering.domain.PlatformPlayerData;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.minecraft.server.network.ServerPlayerEntity;
import java.net.*;
import java.util.Optional;
import java.util.UUID;
public class FabricPlayerData implements PlatformPlayerData {
private final ServerPlayerEntity player;
private final MinecraftDedicatedServer server;
private final String joinAddress;
public FabricPlayerData(ServerPlayerEntity player, MinecraftDedicatedServer server, String joinAddress) {
this.player = player;
this.server = server;
this.joinAddress = joinAddress;
}
@Override
public UUID getUUID() {
return player.getUuid();
}
@Override
public String getName() {
return player.getEntityName();
}
@Override
public Optional<String> getDisplayName() {
return Optional.of(player.getDisplayName().getString());
}
@Override
public Optional<Boolean> isOperator() {
return Optional.of(server.getPlayerManager().getOpList().get(player.getGameProfile()) != null);
}
@Override
public Optional<String> getCurrentWorld() {
return Optional.of(player.getWorld().getRegistryKey().getValue().toString());
}
@Override
public Optional<String> getCurrentGameMode() {
return Optional.of(player.interactionManager.getGameMode().name());
}
@Override
public Optional<InetAddress> getIPAddress() {
return getIPFromSocketAddress();
}
private Optional<InetAddress> getIPFromSocketAddress() {
try {
SocketAddress socketAddress = player.networkHandler.connection.getAddress();
if (socketAddress instanceof InetSocketAddress inetSocketAddress) {
return Optional.of(inetSocketAddress.getAddress());
} else if (socketAddress instanceof UnixDomainSocketAddress) {
// These connections come from the same physical machine
return Optional.of(InetAddress.getLocalHost());
}
} catch (NoSuchMethodError | UnknownHostException e) {
// Ignored
}
return Optional.empty();
}
@Override
public Optional<String> getJoinAddress() {
return Optional.ofNullable(joinAddress);
}
}

View File

@ -21,6 +21,7 @@ import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayNetworkHandler;
@ -81,6 +82,12 @@ public class PlanFabricEvents {
}
});
public static final Event<OnClientHandshake> ON_HANDSHAKE = EventFactory.createArrayBacked(OnClientHandshake.class, callbacks -> packet -> {
for (OnClientHandshake callback : callbacks) {
callback.onHandshake(packet);
}
});
/**
* Called when Plan is enabled.
* <p>
@ -169,6 +176,16 @@ public class PlanFabricEvents {
void onLogin(SocketAddress address, GameProfile profile, Text reason);
}
@FunctionalInterface
public interface OnClientHandshake {
/**
* Called when a player attempts to login
*
* @param packet Handshake packet
*/
void onHandshake(HandshakeC2SPacket packet);
}
@FunctionalInterface
public interface OnEnable {
void onEnable(PlanFabric plugin);

View File

@ -0,0 +1,39 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.events.mixin;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.minecraft.server.network.LocalServerHandshakeNetworkHandler;
import net.minecraft.server.network.ServerHandshakeNetworkHandler;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@Mixin({ServerHandshakeNetworkHandler.class, LocalServerHandshakeNetworkHandler.class})
public class ClientToServerHandshakePacketMixin {
@Inject(method = "onHandshake", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerHandshakeNetworkHandler;onHandshake(Lnet/minecraft/network/packet/c2s/handshake/HandshakeC2SPacket;)V"))
public static void onClientHandshakeFromNetwork(HandshakeC2SPacket packet) {
PlanFabricEvents.ON_HANDSHAKE.invoker().onHandshake(packet);
}
@Inject(method = "onHandshake", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/LocalServerHandshakeNetworkHandler;onHandshake(Lnet/minecraft/network/packet/c2s/handshake/HandshakeC2SPacket;)V"))
public static void onClienthandshakeFromLocal(HandshakeC2SPacket packet) {
PlanFabricEvents.ON_HANDSHAKE.invoker().onHandshake(packet);
}
}

View File

@ -16,91 +16,65 @@
*/
package net.playeranalytics.plan.gathering.listeners.fabric;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.delivery.domain.PlayerName;
import com.djrapitops.plan.delivery.domain.ServerName;
import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.gathering.cache.JoinAddressCache;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer;
import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.transactions.events.*;
import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction;
import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import com.google.common.net.InetAddresses;
import com.mojang.authlib.GameProfile;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.network.NetworkState;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.playeranalytics.plan.gathering.FabricPlayerPositionTracker;
import net.playeranalytics.plan.gathering.domain.FabricPlayerData;
import net.playeranalytics.plan.gathering.listeners.FabricListener;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import javax.inject.Inject;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.concurrent.atomic.AtomicReference;
public class PlayerOnlineListener implements FabricListener {
private final PlanConfig config;
private final Processing processing;
private final PlayerJoinEventConsumer joinEventConsumer;
private final PlayerLeaveEventConsumer leaveEventConsumer;
private final JoinAddressCache joinAddressCache;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ExtensionSvc extensionService;
private final Exporter exporter;
private final GeolocationCache geolocationCache;
private final NicknameCache nicknameCache;
private final SessionCache sessionCache;
private final ErrorLogger errorLogger;
private final MinecraftDedicatedServer server;
private final Map<UUID, String> joinAddresses;
private final AtomicReference<String> joinAddress = new AtomicReference<>();
private boolean isEnabled = false;
@Inject
public PlayerOnlineListener(
PlanConfig config,
Processing processing,
ServerInfo serverInfo,
PlayerJoinEventConsumer joinEventConsumer,
PlayerLeaveEventConsumer leaveEventConsumer,
JoinAddressCache joinAddressCache, ServerInfo serverInfo,
DBSystem dbSystem,
ExtensionSvc extensionService,
Exporter exporter,
GeolocationCache geolocationCache,
NicknameCache nicknameCache,
SessionCache sessionCache,
ErrorLogger errorLogger,
MinecraftDedicatedServer server
) {
this.config = config;
this.processing = processing;
this.joinEventConsumer = joinEventConsumer;
this.leaveEventConsumer = leaveEventConsumer;
this.joinAddressCache = joinAddressCache;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.extensionService = extensionService;
this.exporter = exporter;
this.geolocationCache = geolocationCache;
this.nicknameCache = nicknameCache;
this.sessionCache = sessionCache;
this.errorLogger = errorLogger;
this.server = server;
joinAddresses = new HashMap<>();
}
@Override
@ -115,6 +89,7 @@ public class PlayerOnlineListener implements FabricListener {
if (!this.isEnabled) {
return;
}
beforePlayerQuit(handler.player);
onPlayerQuit(handler.player);
});
PlanFabricEvents.ON_KICKED.register((source, targets, reason) -> {
@ -131,20 +106,33 @@ public class PlayerOnlineListener implements FabricListener {
}
onPlayerLogin(address, profile, reason != null);
});
PlanFabricEvents.ON_HANDSHAKE.register(packet -> {
if (!this.isEnabled) {
return;
}
onHandshake(packet);
});
this.enable();
}
private void onHandshake(HandshakeC2SPacket packet) {
try {
if (packet.getIntendedState() == NetworkState.LOGIN) {
joinAddress.set(packet.getAddress());
}
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), "onHandshake").build());
}
}
public void onPlayerLogin(SocketAddress address, GameProfile profile, boolean banned) {
try {
UUID playerUUID = profile.getId();
ServerUUID serverUUID = serverInfo.getServerUUID();
String joinAddress = address.toString();
if (!joinAddress.isEmpty()) {
joinAddress = joinAddress.substring(0, joinAddress.lastIndexOf(':'));
joinAddresses.put(playerUUID, joinAddress);
dbSystem.getDatabase().executeTransaction(new StoreJoinAddressTransaction(joinAddress));
}
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> banned));
joinAddressCache.put(playerUUID, joinAddress.get());
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, banned));
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), address, profile, banned).build());
}
@ -172,103 +160,46 @@ public class PlayerOnlineListener implements FabricListener {
}
private void actOnJoinEvent(ServerPlayerEntity player) {
UUID playerUUID = player.getUuid();
ServerUUID serverUUID = serverInfo.getServerUUID();
long time = System.currentTimeMillis();
FabricAFKListener.afkTracker.performedAction(playerUUID, time);
String world = player.getWorld().getRegistryKey().getValue().toString();
String gm = player.interactionManager.getGameMode().name();
Database database = dbSystem.getDatabase();
database.executeTransaction(new WorldNameStoreTransaction(serverUUID, world));
Supplier<String> getHostName = () -> getHostname(player);
String playerName = player.getEntityName();
String displayName = player.getDisplayName().getString();
database.executeTransaction(new PlayerServerRegisterTransaction(playerUUID,
System::currentTimeMillis, playerName, serverUUID, getHostName))
.thenRunAsync(() -> {
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
if (gatheringGeolocations) {
gatherGeolocation(player, playerUUID, time, database);
}
database.executeTransaction(new OperatorStatusTransaction(playerUUID, serverUUID, server.getPlayerManager().getOpList().get(player.getGameProfile()) != null));
ActiveSession session = new ActiveSession(playerUUID, serverUUID, time, world, gm);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName()));
session.getExtraData().put(JoinAddress.class, new JoinAddress(getHostName.get()));
sessionCache.cacheSession(playerUUID, session)
.ifPresent(previousSession -> database.executeTransaction(new StoreSessionTransaction(previousSession)));
database.executeTransaction(new NicknameStoreTransaction(
playerUUID, new Nickname(displayName, time, serverUUID),
(uuid, name) -> nicknameCache.getDisplayName(playerUUID).map(name::equals).orElse(false)
));
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
});
}
private void gatherGeolocation(ServerPlayerEntity player, UUID playerUUID, long time, Database database) {
InetSocketAddress socketAddress = (InetSocketAddress) player.networkHandler.connection.getAddress();
if (socketAddress == null) return;
InetAddress address = InetAddresses.forString(socketAddress.getAddress().toString().replace("/", ""));
database.executeTransaction(
new StoreGeoInfoTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
private String getHostname(ServerPlayerEntity player) {
return joinAddresses.get(player.getUuid());
joinEventConsumer.onJoinGameServer(PlayerJoin.builder()
.server(serverInfo.getServer())
.player(new FabricPlayerData(player, server, joinAddressCache.getNullableString(playerUUID)))
.time(time)
.build());
}
// No event priorities on Fabric, so this has to be called with onPlayerQuit()
public void beforePlayerQuit(ServerPlayerEntity player) {
UUID playerUUID = player.getUuid();
String playerName = player.getEntityName();
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
leaveEventConsumer.beforeLeave(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new FabricPlayerData(player, server, null))
.time(System.currentTimeMillis())
.build());
}
public void onPlayerQuit(ServerPlayerEntity player) {
beforePlayerQuit(player);
try {
actOnQuitEvent(player);
FabricPlayerPositionTracker.removePlayer(player.getUuid());
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), player).build());
}
}
private void actOnQuitEvent(ServerPlayerEntity player) {
long time = System.currentTimeMillis();
String playerName = player.getEntityName();
UUID playerUUID = player.getUuid();
ServerUUID serverUUID = serverInfo.getServerUUID();
long time = System.currentTimeMillis();
FabricAFKListener.afkTracker.loggedOut(playerUUID, time);
FabricPlayerPositionTracker.removePlayer(playerUUID);
joinAddresses.remove(playerUUID);
nicknameCache.removeDisplayName(playerUUID);
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> server.getPlayerManager().getUserBanList().contains(player.getGameProfile())));
sessionCache.endSession(playerUUID, time)
.ifPresent(endedSession -> dbSystem.getDatabase().executeTransaction(new StoreSessionTransaction(endedSession)));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
leaveEventConsumer.onLeaveGameServer(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new FabricPlayerData(player, server, null))
.time(time)
.build());
}
@Override
@ -285,6 +216,4 @@ public class PlayerOnlineListener implements FabricListener {
public void disable() {
this.isEnabled = false;
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.domain;
import cn.nukkit.Player;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class NukkitPlayerData implements PlatformPlayerData {
private final Player player;
public NukkitPlayerData(Player player) {
this.player = player;
}
@Override
public UUID getUUID() {
return player.getUniqueId();
}
@Override
public String getName() {
return player.getName();
}
@Override
public Optional<String> getDisplayName() {
return Optional.of(player.getDisplayName());
}
@Override
public Optional<Boolean> isBanned() {
return Optional.of(player.isBanned());
}
@Override
public Optional<Boolean> isOperator() {
return Optional.of(player.isOp());
}
@Override
public Optional<String> getCurrentWorld() {
return Optional.of(player.getLevel().getName());
}
@Override
public Optional<String> getCurrentGameMode() {
return Optional.of(player.getGamemode()).map(GMTimes::magicNumberToGMName);
}
@Override
public Optional<Long> getRegisterDate() {
return Optional.of(TimeUnit.SECONDS.toMillis(player.getFirstPlayed()));
}
@Override
public Optional<InetAddress> getIPAddress() {
return Optional.of(player.getSocketAddress()).map(InetSocketAddress::getAddress);
}
}

View File

@ -24,35 +24,22 @@ import cn.nukkit.event.player.PlayerJoinEvent;
import cn.nukkit.event.player.PlayerKickEvent;
import cn.nukkit.event.player.PlayerLoginEvent;
import cn.nukkit.event.player.PlayerQuitEvent;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.delivery.domain.PlayerName;
import com.djrapitops.plan.delivery.domain.ServerName;
import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.GMTimes;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.gathering.domain.NukkitPlayerData;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer;
import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer;
import com.djrapitops.plan.gathering.listeners.Status;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.transactions.events.*;
import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction;
import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import javax.inject.Inject;
import java.net.InetAddress;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* Event Listener for PlayerJoin, PlayerQuit and PlayerKickEvents.
@ -61,41 +48,26 @@ import java.util.function.Supplier;
*/
public class PlayerOnlineListener implements Listener {
private final PlanConfig config;
private final Processing processing;
private final PlayerJoinEventConsumer joinEventConsumer;
private final PlayerLeaveEventConsumer leaveEventConsumer;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ExtensionSvc extensionService;
private final Exporter exporter;
private final GeolocationCache geolocationCache;
private final NicknameCache nicknameCache;
private final SessionCache sessionCache;
private final ErrorLogger errorLogger;
private final Status status;
@Inject
public PlayerOnlineListener(
PlanConfig config,
Processing processing,
PlayerJoinEventConsumer joinEventConsumer, PlayerLeaveEventConsumer leaveEventConsumer,
ServerInfo serverInfo,
DBSystem dbSystem,
ExtensionSvc extensionService,
Exporter exporter,
GeolocationCache geolocationCache,
NicknameCache nicknameCache,
SessionCache sessionCache,
Status status,
ErrorLogger errorLogger
) {
this.config = config;
this.processing = processing;
this.joinEventConsumer = joinEventConsumer;
this.leaveEventConsumer = leaveEventConsumer;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.extensionService = extensionService;
this.exporter = exporter;
this.geolocationCache = geolocationCache;
this.nicknameCache = nicknameCache;
this.sessionCache = sessionCache;
this.status = status;
this.errorLogger = errorLogger;
}
@ -147,64 +119,29 @@ public class PlayerOnlineListener implements Listener {
}
private void actOnJoinEvent(PlayerJoinEvent event) {
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
ServerUUID serverUUID = serverInfo.getServerUUID();
long time = System.currentTimeMillis();
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
if (playerUUID == null) return; // Can be null when player is not signed in to xbox live
NukkitAFKListener.afkTracker.performedAction(playerUUID, time);
String world = player.getLevel().getName();
String gm = GMTimes.magicNumberToGMName(player.getGamemode());
Database database = dbSystem.getDatabase();
database.executeTransaction(new WorldNameStoreTransaction(serverUUID, world));
InetAddress address = player.getSocketAddress().getAddress();
Supplier<String> getHostName = () -> null;
String playerName = player.getName();
String displayName = player.getDisplayName();
long registerDate = TimeUnit.SECONDS.toMillis(player.getFirstPlayed());
database.executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> registerDate,
playerName, serverUUID, getHostName))
.thenRunAsync(() -> {
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
if (gatheringGeolocations) {
database.executeTransaction(
new StoreGeoInfoTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
dbSystem.getDatabase().executeTransaction(new OperatorStatusTransaction(playerUUID, serverUUID, player.isOp()));
ActiveSession session = new ActiveSession(playerUUID, serverUUID, time, world, gm);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName()));
sessionCache.cacheSession(playerUUID, session)
.map(StoreSessionTransaction::new)
.ifPresent(database::executeTransaction);
database.executeTransaction(new NicknameStoreTransaction(
playerUUID, new Nickname(displayName, time, serverUUID),
(uuid, name) -> nicknameCache.getDisplayName(playerUUID).map(name::equals).orElse(false)
));
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
});
joinEventConsumer.onJoinGameServer(PlayerJoin.builder()
.server(serverInfo.getServer())
.player(new NukkitPlayerData(player))
.time(time)
.build());
}
@EventHandler(priority = EventPriority.NORMAL)
public void beforePlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
String playerName = player.getName();
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
if (event.getPlayer().getUniqueId() == null) return; // Can be null when player is not signed in to xbox live
leaveEventConsumer.beforeLeave(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new NukkitPlayerData(event.getPlayer()))
.time(System.currentTimeMillis())
.build());
}
@EventHandler(priority = EventPriority.MONITOR)
@ -219,22 +156,15 @@ public class PlayerOnlineListener implements Listener {
private void actOnQuitEvent(PlayerQuitEvent event) {
long time = System.currentTimeMillis();
Player player = event.getPlayer();
String playerName = player.getName();
UUID playerUUID = player.getUniqueId();
ServerUUID serverUUID = serverInfo.getServerUUID();
if (playerUUID == null) return; // Can be null when player is not signed in to xbox live
NukkitAFKListener.afkTracker.loggedOut(playerUUID, time);
nicknameCache.removeDisplayName(playerUUID);
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, player::isBanned));
sessionCache.endSession(playerUUID, time)
.ifPresent(endedSession -> dbSystem.getDatabase().executeTransaction(new StoreSessionTransaction(endedSession)));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
leaveEventConsumer.onLeaveGameServer(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new NukkitPlayerData(event.getPlayer()))
.time(System.currentTimeMillis())
.build());
}
}

View File

@ -0,0 +1,89 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.domain;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.value.Value;
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.registry.RegistryTypes;
import org.spongepowered.api.service.ban.BanService;
import java.net.InetAddress;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
public class SpongePlayerData implements PlatformPlayerData {
private final ServerPlayer player;
public SpongePlayerData(ServerPlayer player) {
this.player = player;
}
@Override
public UUID getUUID() {
return player.uniqueId();
}
@Override
public String getName() {
return player.name();
}
@Override
public Optional<String> getDisplayName() {
return Optional.of(LegacyComponentSerializer.legacyAmpersand().serialize(player.displayName().get()));
}
@Override
public Optional<Boolean> isBanned() {
BanService banService = Sponge.server().serviceProvider().banService();
boolean banned = banService.find(player.profile()).join().isPresent();
return Optional.of(banned);
}
@Override
public Optional<String> getJoinAddress() {
return Optional.of(player.connection().virtualHost().getHostString());
}
@Override
public Optional<String> getCurrentWorld() {
return Sponge.game().server().worldManager().worldDirectory(player.world().key())
.map(path -> path.getFileName().toString());
}
@Override
public Optional<String> getCurrentGameMode() {
GameMode gameMode = player.gameMode().get();
String gm = gameMode.key(RegistryTypes.GAME_MODE).value().toUpperCase();
return Optional.of(gm);
}
@Override
public Optional<Long> getRegisterDate() {
return player.firstJoined().map(Value::get).map(Instant::toEpochMilli);
}
@Override
public Optional<InetAddress> getIPAddress() {
return Optional.of(player.connection().address().getAddress());
}
}

View File

@ -16,48 +16,33 @@
*/
package com.djrapitops.plan.gathering.listeners.sponge;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.delivery.domain.PlayerName;
import com.djrapitops.plan.delivery.domain.ServerName;
import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.gathering.domain.SpongePlayerData;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer;
import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer;
import com.djrapitops.plan.gathering.listeners.Status;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.transactions.events.*;
import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction;
import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.Order;
import org.spongepowered.api.event.entity.living.player.KickPlayerEvent;
import org.spongepowered.api.event.network.ServerSideConnectionEvent;
import org.spongepowered.api.profile.GameProfile;
import org.spongepowered.api.registry.RegistryTypes;
import org.spongepowered.api.service.ban.Ban;
import org.spongepowered.api.service.ban.BanService;
import javax.inject.Inject;
import java.net.InetAddress;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
/**
* Listener for Player Join/Leave on Sponge.
@ -66,40 +51,27 @@ import java.util.function.Supplier;
*/
public class PlayerOnlineListener {
private final PlanConfig config;
private final Processing processing;
private final PlayerJoinEventConsumer joinEventConsumer;
private final PlayerLeaveEventConsumer leaveEventConsumer;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ExtensionSvc extensionService;
private final Exporter exporter;
private final GeolocationCache geolocationCache;
private final NicknameCache nicknameCache;
private final SessionCache sessionCache;
private final Status status;
private final ErrorLogger errorLogger;
@Inject
public PlayerOnlineListener(
PlanConfig config,
Processing processing,
PlayerJoinEventConsumer joinEventConsumer,
PlayerLeaveEventConsumer leaveEventConsumer,
ServerInfo serverInfo,
DBSystem dbSystem,
ExtensionSvc extensionService,
Exporter exporter, GeolocationCache geolocationCache,
NicknameCache nicknameCache,
SessionCache sessionCache,
Status status,
ErrorLogger errorLogger
) {
this.config = config;
this.processing = processing;
this.joinEventConsumer = joinEventConsumer;
this.leaveEventConsumer = leaveEventConsumer;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.extensionService = extensionService;
this.exporter = exporter;
this.geolocationCache = geolocationCache;
this.nicknameCache = nicknameCache;
this.sessionCache = sessionCache;
this.status = status;
this.errorLogger = errorLogger;
}
@ -149,65 +121,25 @@ public class PlayerOnlineListener {
}
private void actOnJoinEvent(ServerSideConnectionEvent.Join event) {
ServerPlayer player = event.player();
UUID playerUUID = player.uniqueId();
ServerUUID serverUUID = serverInfo.getServerUUID();
long time = System.currentTimeMillis();
ServerPlayer player = event.player();
UUID playerUUID = player.uniqueId();
SpongeAFKListener.afkTracker.performedAction(playerUUID, time);
String world = Sponge.game().server().worldManager().worldDirectory(player.world().key())
.map(path -> path.getFileName().toString()).orElse("Unknown");
GameMode gameMode = player.gameMode().get();
String gm = gameMode.key(RegistryTypes.GAME_MODE).value().toUpperCase();
Database database = dbSystem.getDatabase();
database.executeTransaction(new WorldNameStoreTransaction(serverUUID, world));
InetAddress address = player.connection().address().getAddress();
Supplier<String> getHostName = () -> player.connection().virtualHost().getHostString();
String playerName = player.name();
String displayName = LegacyComponentSerializer.legacyAmpersand().serialize(player.displayName().get());
database.executeTransaction(new PlayerServerRegisterTransaction(playerUUID, () -> time,
playerName, serverUUID, getHostName))
.thenRunAsync(() -> {
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
if (gatheringGeolocations) {
database.executeTransaction(
new StoreGeoInfoTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
database.executeTransaction(new StoreJoinAddressTransaction(getHostName));
ActiveSession session = new ActiveSession(playerUUID, serverUUID, time, world, gm);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName()));
session.getExtraData().put(JoinAddress.class, new JoinAddress(getHostName.get()));
sessionCache.cacheSession(playerUUID, session)
.map(StoreSessionTransaction::new)
.ifPresent(database::executeTransaction);
database.executeTransaction(new NicknameStoreTransaction(
playerUUID, new Nickname(displayName, time, serverUUID),
(uuid, name) -> nicknameCache.getDisplayName(playerUUID).map(name::equals).orElse(false)
));
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
});
joinEventConsumer.onJoinGameServer(PlayerJoin.builder()
.server(serverInfo.getServer())
.player(new SpongePlayerData(player))
.time(time)
.build());
}
@Listener(order = Order.DEFAULT)
public void beforeQuit(ServerSideConnectionEvent.Disconnect event) {
Player player = event.player();
UUID playerUUID = player.uniqueId();
String playerName = player.name();
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
leaveEventConsumer.beforeLeave(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new SpongePlayerData(event.player()))
.time(System.currentTimeMillis())
.build());
}
@Listener(order = Order.POST)
@ -222,21 +154,13 @@ public class PlayerOnlineListener {
private void actOnQuitEvent(ServerSideConnectionEvent.Disconnect event) {
long time = System.currentTimeMillis();
Player player = event.player();
String playerName = player.name();
UUID playerUUID = player.uniqueId();
ServerUUID serverUUID = serverInfo.getServerUUID();
SpongeAFKListener.afkTracker.loggedOut(playerUUID, time);
nicknameCache.removeDisplayName(playerUUID);
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> isBanned(player.profile())));
sessionCache.endSession(playerUUID, time)
.ifPresent(endedSession -> dbSystem.getDatabase().executeTransaction(new StoreSessionTransaction(endedSession)));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
leaveEventConsumer.onLeaveGameServer(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new SpongePlayerData(event.player()))
.time(System.currentTimeMillis())
.build());
}
}

View File

@ -0,0 +1,53 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering.domain;
import com.velocitypowered.api.proxy.Player;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.UUID;
public class VelocityPlayerData implements PlatformPlayerData {
private final Player player;
public VelocityPlayerData(Player player) {
this.player = player;
}
@Override
public UUID getUUID() {
return player.getUniqueId();
}
@Override
public String getName() {
return player.getUsername();
}
@Override
public Optional<InetAddress> getIPAddress() {
return Optional.of(player.getRemoteAddress().getAddress());
}
@Override
public Optional<String> getJoinAddress() {
return player.getVirtualHost().map(InetSocketAddress::getHostString);
}
}

View File

@ -16,23 +16,13 @@
*/
package com.djrapitops.plan.gathering.listeners.velocity;
import com.djrapitops.plan.delivery.domain.PlayerName;
import com.djrapitops.plan.delivery.domain.ServerName;
import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.gathering.domain.VelocityPlayerData;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer;
import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer;
import com.djrapitops.plan.gathering.events.PlayerSwitchServerEventConsumer;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.transactions.events.PlayerRegisterTransaction;
import com.djrapitops.plan.storage.database.transactions.events.StoreGeoInfoTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import com.velocitypowered.api.event.PostOrder;
@ -44,8 +34,6 @@ import com.velocitypowered.api.proxy.Player;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.net.InetAddress;
import java.util.UUID;
/**
* Player Join listener for Velocity.
@ -57,35 +45,24 @@ import java.util.UUID;
@Singleton
public class PlayerOnlineListener {
private final PlanConfig config;
private final Processing processing;
private final DBSystem dbSystem;
private final ExtensionSvc extensionService;
private final Exporter exporter;
private final GeolocationCache geolocationCache;
private final SessionCache sessionCache;
private final PlayerJoinEventConsumer joinEventConsumer;
private final PlayerLeaveEventConsumer leaveEventConsumer;
private final PlayerSwitchServerEventConsumer switchServerEventConsumer;
private final ServerInfo serverInfo;
private final ErrorLogger errorLogger;
@Inject
public PlayerOnlineListener(
PlanConfig config,
Processing processing,
DBSystem dbSystem,
ExtensionSvc extensionService,
Exporter exporter,
GeolocationCache geolocationCache,
SessionCache sessionCache,
PlayerJoinEventConsumer joinEventConsumer,
PlayerLeaveEventConsumer leaveEventConsumer,
PlayerSwitchServerEventConsumer switchServerEventConsumer,
ServerInfo serverInfo,
ErrorLogger errorLogger
) {
this.config = config;
this.processing = processing;
this.dbSystem = dbSystem;
this.extensionService = extensionService;
this.exporter = exporter;
this.geolocationCache = geolocationCache;
this.sessionCache = sessionCache;
this.joinEventConsumer = joinEventConsumer;
this.leaveEventConsumer = leaveEventConsumer;
this.switchServerEventConsumer = switchServerEventConsumer;
this.serverInfo = serverInfo;
this.errorLogger = errorLogger;
}
@ -101,63 +78,37 @@ public class PlayerOnlineListener {
public void actOnLogin(PostLoginEvent event) {
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
String playerName = player.getUsername();
InetAddress address = player.getRemoteAddress().getAddress();
long time = System.currentTimeMillis();
ActiveSession session = new ActiveSession(playerUUID, serverInfo.getServerUUID(), time, null, null);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName("Proxy Server"));
sessionCache.cacheSession(playerUUID, session);
Database database = dbSystem.getDatabase();
database.executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> time, playerName))
.thenRunAsync(() -> {
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
if (gatheringGeolocations) {
database.executeTransaction(
new StoreGeoInfoTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
});
joinEventConsumer.onJoinProxyServer(PlayerJoin.builder()
.server(serverInfo.getServer())
.player(new VelocityPlayerData(player))
.time(time)
.build());
}
@Subscribe(order = PostOrder.NORMAL)
public void beforeLogout(DisconnectEvent event) {
Player player = event.getPlayer();
UUID playerUUID = player.getUniqueId();
String playerName = player.getUsername();
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
leaveEventConsumer.beforeLeave(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new VelocityPlayerData(event.getPlayer()))
.time(System.currentTimeMillis())
.build());
}
@Subscribe(order = PostOrder.LAST)
public void onLogout(DisconnectEvent event) {
try {
actOnLogout(event);
leaveEventConsumer.onLeaveProxyServer(PlayerLeave.builder()
.server(serverInfo.getServer())
.player(new VelocityPlayerData(event.getPlayer()))
.time(System.currentTimeMillis())
.build());
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(event).build());
}
}
public void actOnLogout(DisconnectEvent event) {
Player player = event.getPlayer();
String playerName = player.getUsername();
UUID playerUUID = player.getUniqueId();
sessionCache.endSession(playerUUID, System.currentTimeMillis());
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
}
@Subscribe(order = PostOrder.LAST)
public void onServerSwitch(ServerConnectedEvent event) {
try {
@ -169,18 +120,8 @@ public class PlayerOnlineListener {
public void actOnServerSwitch(ServerConnectedEvent event) {
Player player = event.getPlayer();
String playerName = player.getUsername();
UUID playerUUID = player.getUniqueId();
long time = System.currentTimeMillis();
// Replaces the current session in the cache.
ActiveSession session = new ActiveSession(playerUUID, serverInfo.getServerUUID(), time, null, null);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName("Proxy Server"));
sessionCache.cacheSession(playerUUID, session);
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
switchServerEventConsumer.onServerSwitch(new VelocityPlayerData(player), time);
}
}