diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d94202fe2..891d4dd64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 }} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/domain/BukkitPlayerData.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/domain/BukkitPlayerData.java new file mode 100644 index 000000000..c7ecd2fc4 --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/domain/BukkitPlayerData.java @@ -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 . + */ +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 getDisplayName() { + return Optional.of(player.getDisplayName()); + } + + @Override + public Optional isBanned() { + return Optional.of(player.isBanned()); + } + + @Override + public Optional isOperator() { + return Optional.of(player.isOp()); + } + + @Override + public Optional getJoinAddress() { + return Optional.ofNullable(joinAddress); + } + + @Override + public Optional getCurrentWorld() { + return Optional.of(player.getWorld().getName()); + } + + @Override + public Optional getCurrentGameMode() { + return Optional.ofNullable(player.getGameMode()).map(Enum::name); + } + + @Override + public Optional getRegisterDate() { + return Optional.of(player.getFirstPlayed()); + } + + @Override + public Optional getIPAddress() { + return Optional.ofNullable(player.getAddress()).map(InetSocketAddress::getAddress); + } +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java index a577e9b51..e36941eaf 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java @@ -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 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 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()); } } diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/modules/bukkit/BukkitEventPipelineModule.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/modules/bukkit/BukkitEventPipelineModule.java deleted file mode 100644 index 12759ff40..000000000 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/modules/bukkit/BukkitEventPipelineModule.java +++ /dev/null @@ -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 . - */ -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 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 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(); - } -} diff --git a/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/domain/BungeePlayerData.java b/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/domain/BungeePlayerData.java new file mode 100644 index 000000000..21c8d6401 --- /dev/null +++ b/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/domain/BungeePlayerData.java @@ -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 . + */ +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 getIPAddress() { + Optional ip = getIPFromSocketAddress(); + if (ip.isPresent()) return ip; + return getIpFromOldMethod(); + } + + @SuppressWarnings("deprecation") // ProxiedPlayer#getAddress is deprecated + private Optional getIpFromOldMethod() { + try { + return Optional.ofNullable(player.getAddress()).map(InetSocketAddress::getAddress); + } catch (NoSuchMethodError e) { + return Optional.empty(); + } + } + + private Optional 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(); + } +} diff --git a/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/listeners/bungee/PlayerOnlineListener.java b/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/listeners/bungee/PlayerOnlineListener.java index d84f6e8e9..b8c02df69 100644 --- a/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/listeners/bungee/PlayerOnlineListener.java +++ b/Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/listeners/bungee/PlayerOnlineListener.java @@ -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)); - } - } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java index b0ffa738d..6c15d5b5c 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java @@ -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") diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java index 5553d2161..f25a882f4 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java @@ -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 = 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); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/cache/JoinAddressCache.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/cache/JoinAddressCache.java index e5b74b27d..ce8b25e76 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/gathering/cache/JoinAddressCache.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/cache/JoinAddressCache.java @@ -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 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) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/cache/SessionCache.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/cache/SessionCache.java index 600d90ca1..d9afef3a2 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/gathering/cache/SessionCache.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/cache/SessionCache.java @@ -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 endSession(UUID playerUUID, PlayerLeave leave) { - return endSession(playerUUID, leave.getTime()); - } - public Optional endSession(UUID playerUUID, long time) { return endSession(playerUUID, time, ACTIVE_SESSIONS.get(playerUUID)); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/ActiveSession.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/ActiveSession.java index b94b54403..5d6705162 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/ActiveSession.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/ActiveSession.java @@ -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()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/GMTimes.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/GMTimes.java index 79ce61e64..1d9b1d6ac 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/GMTimes.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/GMTimes.java @@ -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 times, String lastState, long lastStateChange) { super(times, lastState, lastStateChange); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/PlatformPlayerData.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/PlatformPlayerData.java new file mode 100644 index 000000000..d68027985 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/PlatformPlayerData.java @@ -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 . + */ +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 getDisplayName() { + return Optional.empty(); + } + + default Optional isBanned() { + return Optional.empty(); + } + + default Optional isOperator() { + return Optional.empty(); + } + + default Optional getJoinAddress() { + return Optional.empty(); + } + + default Optional getCurrentWorld() { + return Optional.empty(); + } + + default Optional getCurrentGameMode() { + return Optional.empty(); + } + + default Optional getRegisterDate() { + return Optional.empty(); + } + + default Optional getIPAddress() { + return Optional.empty(); + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/event/PlayerJoin.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/event/PlayerJoin.java index ecfa4bee6..65c53fe89 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/event/PlayerJoin.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/event/PlayerJoin.java @@ -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);} } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/event/PlayerLeave.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/event/PlayerLeave.java index afa570e36..e0c3761f7 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/event/PlayerLeave.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/domain/event/PlayerLeave.java @@ -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);} } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/events/PlayerJoinEventConsumer.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/events/PlayerJoinEventConsumer.java new file mode 100644 index 000000000..073e65813 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/events/PlayerJoinEventConsumer.java @@ -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 . + */ +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 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())); + } + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/events/PlayerLeaveEventConsumer.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/events/PlayerLeaveEventConsumer.java new file mode 100644 index 000000000..fb75536b7 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/events/PlayerLeaveEventConsumer.java @@ -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 . + */ +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 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); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/events/PlayerSwitchServerEventConsumer.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/events/PlayerSwitchServerEventConsumer.java new file mode 100644 index 000000000..ac22fe402 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/events/PlayerSwitchServerEventConsumer.java @@ -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 . + */ +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); + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/gathering/geolocation/GeolocationCache.java b/Plan/common/src/main/java/com/djrapitops/plan/gathering/geolocation/GeolocationCache.java index eb603285f..b1dd2d502 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/gathering/geolocation/GeolocationCache.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/gathering/geolocation/GeolocationCache.java @@ -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 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)); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/modules/DataPipelineModule.java b/Plan/common/src/main/java/com/djrapitops/plan/modules/DataPipelineModule.java deleted file mode 100644 index 1161e2520..000000000 --- a/Plan/common/src/main/java/com/djrapitops/plan/modules/DataPipelineModule.java +++ /dev/null @@ -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 . - */ -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)); - } - -} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/processing/Processing.java b/Plan/common/src/main/java/com/djrapitops/plan/processing/Processing.java index cac480439..b2533103e 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/processing/Processing.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/processing/Processing.java @@ -70,19 +70,19 @@ public class Processing implements SubSystem { submitNonCritical(runnable); } - public void submitNonCritical(Runnable runnable) { + public CompletableFuture 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 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; + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java index 3ca92c0f5..16e4e8261 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java @@ -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}"), diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java index 1c5344453..11b4d8587 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java @@ -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; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java index c7350a964..feebdcccb 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java @@ -430,4 +430,8 @@ public abstract class SQLDB extends AbstractDatabase { public boolean shouldDropUnimportantTransactions() { return dropUnimportantTransactions.get(); } + + public int getTransactionQueueSize() { + return transactionQueueSize.get(); + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/BanStatusTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/BanStatusTransaction.java index 301153a45..ca4a4dd07 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/BanStatusTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/BanStatusTransaction.java @@ -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; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerServerRegisterTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerServerRegisterTransaction.java index 89f73b5b4..941ea6faa 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerServerRegisterTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/PlayerServerRegisterTransaction.java @@ -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(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/BadFabricJoinAddressValuePatch.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/BadFabricJoinAddressValuePatch.java new file mode 100644 index 000000000..0a67d1667 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/patches/BadFabricJoinAddressValuePatch.java @@ -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 . + */ +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 + ")"); + } +} diff --git a/Plan/common/src/test/java/com/djrapitops/plan/commands/PlanCommandTest.java b/Plan/common/src/test/java/com/djrapitops/plan/commands/PlanCommandTest.java index 5965ec1de..7b63848d6 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/commands/PlanCommandTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/commands/PlanCommandTest.java @@ -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); diff --git a/Plan/common/src/test/java/com/djrapitops/plan/gathering/cache/JoinAddressCacheTest.java b/Plan/common/src/test/java/com/djrapitops/plan/gathering/cache/JoinAddressCacheTest.java new file mode 100644 index 000000000..0e37ae62a --- /dev/null +++ b/Plan/common/src/test/java/com/djrapitops/plan/gathering/cache/JoinAddressCacheTest.java @@ -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 . + */ +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 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 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(); + } +} \ No newline at end of file diff --git a/Plan/common/src/test/java/com/djrapitops/plan/gathering/events/PlayerJoinEventConsumerTest.java b/Plan/common/src/test/java/com/djrapitops/plan/gathering/events/PlayerJoinEventConsumerTest.java new file mode 100644 index 000000000..601c73a85 --- /dev/null +++ b/Plan/common/src/test/java/com/djrapitops/plan/gathering/events/PlayerJoinEventConsumerTest.java @@ -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 . + */ +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 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 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 expected = List.of("World"); + List 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 expected = List.of("play.testjoinaddress.com", JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); + List 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 expected = List.of(new BaseUser(TestConstants.PLAYER_ONE_UUID, TestConstants.PLAYER_ONE_NAME, 1234L, 0)); + Collection result = database.query(BaseUserQueries.fetchAllBaseUsers()); + assertEquals(expected, result); + } + + @Test + void joiningProxyServerStoresUser(Database database) { + PlayerJoin join = createPlayerJoin(createTestPlayer()); + + underTest.onJoinProxyServer(join); + waitUntilDatabaseIsDone(database); + + Collection expected = List.of(new BaseUser(TestConstants.PLAYER_ONE_UUID, TestConstants.PLAYER_ONE_NAME, join.getTime(), 0)); + Collection 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 expected = Set.of(new UserInfo(TestConstants.PLAYER_ONE_UUID, serverUUID, 1234L, false, "play.testjoinaddress.com", false)); + Set 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 expected = List.of("Nickname"); + List 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 expected = List.of("United States"); + List 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 expected = List.of("United States"); + List 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 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()); + } + +} \ No newline at end of file diff --git a/Plan/common/src/test/java/com/djrapitops/plan/gathering/events/PlayerLeaveEventConsumerTest.java b/Plan/common/src/test/java/com/djrapitops/plan/gathering/events/PlayerLeaveEventConsumerTest.java new file mode 100644 index 000000000..a9933fb9f --- /dev/null +++ b/Plan/common/src/test/java/com/djrapitops/plan/gathering/events/PlayerLeaveEventConsumerTest.java @@ -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 . + */ +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 expected = List.of(new FinishedSession( + TestConstants.PLAYER_ONE_UUID, + serverUUID, + sessionStart, + leave.getTime(), + 0L, + extraData + )); + List 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 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); + } + } +} \ No newline at end of file diff --git a/Plan/common/src/test/java/com/djrapitops/plan/gathering/geolocation/GeolocationTest.java b/Plan/common/src/test/java/com/djrapitops/plan/gathering/geolocation/GeolocationTest.java index a55dde9ca..345aceb57 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/gathering/geolocation/GeolocationTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/gathering/geolocation/GeolocationTest.java @@ -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()); diff --git a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTest.java b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTest.java index d62c36519..d1bdf2ad2 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTest.java @@ -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 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 expected = new ArrayList<>(List.of("test1", JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)); + List sessions = db().query(SessionQueries.fetchAllSessions()); + System.out.println(sessions); + List 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 expected = Set.of( + new UserInfo(playerUUID, serverUUID(), time, false, "test1", false), + new UserInfo(playerUUID, randomSecondServer, time, false, null, false) + ); + Set result = db().query(UserInfoQueries.fetchUserInformationOfUser(playerUUID)); + assertEquals(expected, result); + } } diff --git a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/ExtensionsDatabaseTest.java b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/ExtensionsDatabaseTest.java index b836d7392..26f0e2463 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/ExtensionsDatabaseTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/ExtensionsDatabaseTest.java @@ -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(); diff --git a/Plan/common/src/test/java/extension/FullSystemExtension.java b/Plan/common/src/test/java/extension/FullSystemExtension.java new file mode 100644 index 000000000..43f081516 --- /dev/null +++ b/Plan/common/src/test/java/extension/FullSystemExtension.java @@ -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 . + */ +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()); + } +} diff --git a/Plan/common/src/test/java/utilities/dagger/PlanPluginComponent.java b/Plan/common/src/test/java/utilities/dagger/PlanPluginComponent.java index 5afb8c792..27f762a61 100644 --- a/Plan/common/src/test/java/utilities/dagger/PlanPluginComponent.java +++ b/Plan/common/src/test/java/utilities/dagger/PlanPluginComponent.java @@ -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 diff --git a/Plan/common/src/test/java/utilities/mocks/objects/TestPlayerData.java b/Plan/common/src/test/java/utilities/mocks/objects/TestPlayerData.java new file mode 100644 index 000000000..c4e42d798 --- /dev/null +++ b/Plan/common/src/test/java/utilities/mocks/objects/TestPlayerData.java @@ -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 . + */ +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 getDisplayName() { + return Optional.ofNullable(displayName); + } + + public TestPlayerData setDisplayName(String displayName) { + this.displayName = displayName; + return this; + } + + @Override + public Optional isBanned() { + return Optional.ofNullable(banned); + } + + @Override + public Optional isOperator() { + return Optional.ofNullable(operator); + } + + @Override + public Optional getJoinAddress() { + return Optional.ofNullable(joinAddress); + } + + public TestPlayerData setJoinAddress(String joinAddress) { + this.joinAddress = joinAddress; + return this; + } + + @Override + public Optional getCurrentWorld() { + return Optional.ofNullable(currentWorld); + } + + public TestPlayerData setCurrentWorld(String currentWorld) { + this.currentWorld = currentWorld; + return this; + } + + @Override + public Optional getCurrentGameMode() { + return Optional.ofNullable(currentGameMode); + } + + public TestPlayerData setCurrentGameMode(String currentGameMode) { + this.currentGameMode = currentGameMode; + return this; + } + + @Override + public Optional getRegisterDate() { + return Optional.ofNullable(registerDate); + } + + public TestPlayerData setRegisterDate(Long registerDate) { + this.registerDate = registerDate; + return this; + } + + @Override + public Optional 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; + } +} diff --git a/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/domain/FabricPlayerData.java b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/domain/FabricPlayerData.java new file mode 100644 index 000000000..cd3930beb --- /dev/null +++ b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/domain/FabricPlayerData.java @@ -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 . + */ +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 getDisplayName() { + return Optional.of(player.getDisplayName().getString()); + } + + @Override + public Optional isOperator() { + return Optional.of(server.getPlayerManager().getOpList().get(player.getGameProfile()) != null); + } + + @Override + public Optional getCurrentWorld() { + return Optional.of(player.getWorld().getRegistryKey().getValue().toString()); + } + + @Override + public Optional getCurrentGameMode() { + return Optional.of(player.interactionManager.getGameMode().name()); + } + + @Override + public Optional getIPAddress() { + return getIPFromSocketAddress(); + } + + private Optional 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 getJoinAddress() { + return Optional.ofNullable(joinAddress); + } +} diff --git a/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/PlanFabricEvents.java b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/PlanFabricEvents.java index b260b8da7..644207b78 100644 --- a/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/PlanFabricEvents.java +++ b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/PlanFabricEvents.java @@ -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 ON_HANDSHAKE = EventFactory.createArrayBacked(OnClientHandshake.class, callbacks -> packet -> { + for (OnClientHandshake callback : callbacks) { + callback.onHandshake(packet); + } + }); + /** * Called when Plan is enabled. *

@@ -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); diff --git a/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/mixin/ClientToServerHandshakePacketMixin.java b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/mixin/ClientToServerHandshakePacketMixin.java new file mode 100644 index 000000000..370179447 --- /dev/null +++ b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/events/mixin/ClientToServerHandshakePacketMixin.java @@ -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 . + */ +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); + } +} diff --git a/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/fabric/PlayerOnlineListener.java b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/fabric/PlayerOnlineListener.java index 36efa61c3..215638765 100644 --- a/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/fabric/PlayerOnlineListener.java +++ b/Plan/fabric/src/main/java/net/playeranalytics/plan/gathering/listeners/fabric/PlayerOnlineListener.java @@ -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 joinAddresses; + private final AtomicReference 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 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; } - - } diff --git a/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/domain/NukkitPlayerData.java b/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/domain/NukkitPlayerData.java new file mode 100644 index 000000000..071ef2431 --- /dev/null +++ b/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/domain/NukkitPlayerData.java @@ -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 . + */ +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 getDisplayName() { + return Optional.of(player.getDisplayName()); + } + + @Override + public Optional isBanned() { + return Optional.of(player.isBanned()); + } + + @Override + public Optional isOperator() { + return Optional.of(player.isOp()); + } + + @Override + public Optional getCurrentWorld() { + return Optional.of(player.getLevel().getName()); + } + + @Override + public Optional getCurrentGameMode() { + return Optional.of(player.getGamemode()).map(GMTimes::magicNumberToGMName); + } + + @Override + public Optional getRegisterDate() { + return Optional.of(TimeUnit.SECONDS.toMillis(player.getFirstPlayed())); + } + + @Override + public Optional getIPAddress() { + return Optional.of(player.getSocketAddress()).map(InetSocketAddress::getAddress); + } +} diff --git a/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/listeners/nukkit/PlayerOnlineListener.java b/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/listeners/nukkit/PlayerOnlineListener.java index ab54b4aa7..572240351 100644 --- a/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/listeners/nukkit/PlayerOnlineListener.java +++ b/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/listeners/nukkit/PlayerOnlineListener.java @@ -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 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()); } } diff --git a/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/domain/SpongePlayerData.java b/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/domain/SpongePlayerData.java new file mode 100644 index 000000000..184532d24 --- /dev/null +++ b/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/domain/SpongePlayerData.java @@ -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 . + */ +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 getDisplayName() { + return Optional.of(LegacyComponentSerializer.legacyAmpersand().serialize(player.displayName().get())); + } + + @Override + public Optional isBanned() { + BanService banService = Sponge.server().serviceProvider().banService(); + boolean banned = banService.find(player.profile()).join().isPresent(); + return Optional.of(banned); + } + + @Override + public Optional getJoinAddress() { + return Optional.of(player.connection().virtualHost().getHostString()); + } + + @Override + public Optional getCurrentWorld() { + return Sponge.game().server().worldManager().worldDirectory(player.world().key()) + .map(path -> path.getFileName().toString()); + } + + @Override + public Optional getCurrentGameMode() { + GameMode gameMode = player.gameMode().get(); + String gm = gameMode.key(RegistryTypes.GAME_MODE).value().toUpperCase(); + return Optional.of(gm); + } + + @Override + public Optional getRegisterDate() { + return player.firstJoined().map(Value::get).map(Instant::toEpochMilli); + } + + @Override + public Optional getIPAddress() { + return Optional.of(player.connection().address().getAddress()); + } +} diff --git a/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/listeners/sponge/PlayerOnlineListener.java b/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/listeners/sponge/PlayerOnlineListener.java index c622a1f60..49ffca1bc 100644 --- a/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/listeners/sponge/PlayerOnlineListener.java +++ b/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/listeners/sponge/PlayerOnlineListener.java @@ -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 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()); } } diff --git a/Plan/velocity/src/main/java/com/djrapitops/plan/gathering/domain/VelocityPlayerData.java b/Plan/velocity/src/main/java/com/djrapitops/plan/gathering/domain/VelocityPlayerData.java new file mode 100644 index 000000000..9deba9f6e --- /dev/null +++ b/Plan/velocity/src/main/java/com/djrapitops/plan/gathering/domain/VelocityPlayerData.java @@ -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 . + */ +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 getIPAddress() { + return Optional.of(player.getRemoteAddress().getAddress()); + } + + @Override + public Optional getJoinAddress() { + return player.getVirtualHost().map(InetSocketAddress::getHostString); + } +} diff --git a/Plan/velocity/src/main/java/com/djrapitops/plan/gathering/listeners/velocity/PlayerOnlineListener.java b/Plan/velocity/src/main/java/com/djrapitops/plan/gathering/listeners/velocity/PlayerOnlineListener.java index 829bcadd7..a7f11c7b0 100644 --- a/Plan/velocity/src/main/java/com/djrapitops/plan/gathering/listeners/velocity/PlayerOnlineListener.java +++ b/Plan/velocity/src/main/java/com/djrapitops/plan/gathering/listeners/velocity/PlayerOnlineListener.java @@ -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); } }