mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-01-21 23:51:29 +01:00
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:
parent
d1802ff7ba
commit
b646e18c68
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@ -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 }}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);}
|
||||
}
|
||||
}
|
||||
|
@ -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);}
|
||||
}
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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}"),
|
||||
|
@ -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;
|
||||
|
@ -430,4 +430,8 @@ public abstract class SQLDB extends AbstractDatabase {
|
||||
public boolean shouldDropUnimportantTransactions() {
|
||||
return dropUnimportantTransactions.get();
|
||||
}
|
||||
|
||||
public int getTransactionQueueSize() {
|
||||
return transactionQueueSize.get();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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 + ")");
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
83
Plan/common/src/test/java/com/djrapitops/plan/gathering/cache/JoinAddressCacheTest.java
vendored
Normal file
83
Plan/common/src/test/java/com/djrapitops/plan/gathering/cache/JoinAddressCacheTest.java
vendored
Normal 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();
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
110
Plan/common/src/test/java/extension/FullSystemExtension.java
Normal file
110
Plan/common/src/test/java/extension/FullSystemExtension.java
Normal 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());
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user