From 9acce5d595a850fa75ad729ea5d50c22d57d8f33 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Wed, 15 Aug 2018 15:00:38 +0300 Subject: [PATCH 01/76] Update .travis.yml attempt to reduce build failiures --- .travis.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index fa2df3f4e..184c62fae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,14 +2,11 @@ language: java sudo: false install: true before_install: - - pwd - cd PlanPluginBridge - - ls - mvn install:install-file -Dfile=./PlanPluginBridge-4.4.0.jar -DpomFile=./pom.xml - cd ../Plan - - ls install: - - mvn install -DskipTests + - mvn clean addons: sonarcloud: organization: "player-analytics-plan" @@ -18,9 +15,8 @@ addons: jdk: - oraclejdk8 script: - - pwd - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then mvn test; fi' - - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar; fi' + - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then mvn org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar; fi' cache: directories: - '$HOME/.m2/repository' From 53851bb6da274db0150252bcae307e9b4461e0c1 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 20 Aug 2018 10:36:32 +0300 Subject: [PATCH 02/76] Updated hikaricp to 3.2.0 --- Plan/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plan/pom.xml b/Plan/pom.xml index 0c307b7e6..baf436b60 100644 --- a/Plan/pom.xml +++ b/Plan/pom.xml @@ -112,7 +112,7 @@ com.zaxxer HikariCP - 3.1.0 + 3.2.0 From 151d09f284b2357821c67b2ff6ac2ddb3a3e0874 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 20 Aug 2018 10:59:14 +0300 Subject: [PATCH 03/76] Fixes longest world time calculation #690 --- .../com/djrapitops/plan/data/container/Session.java | 10 +++++++--- .../java/com/djrapitops/plan/data/time/WorldTimes.java | 7 +++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java b/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java index 4b0ce2ce0..4a6fb62c4 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java @@ -154,11 +154,11 @@ public class Session extends DataContainer implements DateHolder { } public void setWorldTimes(WorldTimes worldTimes) { - putRawData(SessionKeys.WORLD_TIMES, worldTimes); + this.worldTimes = worldTimes; } public void setPlayerKills(List playerKills) { - putRawData(SessionKeys.PLAYER_KILLS, playerKills); + this.playerKills = playerKills; } public boolean isFetchedFromDB() { @@ -231,7 +231,6 @@ public class Session extends DataContainer implements DateHolder { } Map playtimePerAlias = worldTimes.getPlaytimePerAlias(); - long total = worldTimes.getTotal(); long longest = 0; String theWorld = "-"; @@ -244,6 +243,11 @@ public class Session extends DataContainer implements DateHolder { } } + long total = worldTimes.getTotal(); + // Prevent arithmetic error if 0 + if (total <= 0) { + total = -1; + } double quotient = longest * 1.0 / total; return theWorld + " (" + Formatters.percentage().apply(quotient) + ")"; diff --git a/Plan/src/main/java/com/djrapitops/plan/data/time/WorldTimes.java b/Plan/src/main/java/com/djrapitops/plan/data/time/WorldTimes.java index 1554528bb..24ec27938 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/time/WorldTimes.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/time/WorldTimes.java @@ -170,8 +170,11 @@ public class WorldTimes { } public Map getPlaytimePerAlias() { - Map playtimePerWorld = getWorldTimes() // WorldTimes Map - .entrySet().stream() + if (times.isEmpty()) { + return new HashMap<>(); + } + + Map playtimePerWorld = times.entrySet().stream() // WorldTimes Map .collect(Collectors.toMap( Map.Entry::getKey, entry -> entry.getValue().getTotal() // GMTimes.getTotal From e165164a7f80686dabfc9c696e50ac3f43b0a34b Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 20 Aug 2018 11:04:28 +0300 Subject: [PATCH 04/76] Fixes wrong relative link on server page #689 --- Plan/src/main/resources/web/server.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Plan/src/main/resources/web/server.html b/Plan/src/main/resources/web/server.html index 17a79e9e8..5438fa41a 100644 --- a/Plan/src/main/resources/web/server.html +++ b/Plan/src/main/resources/web/server.html @@ -1148,9 +1148,9 @@ - - - + + + From 5e2c540522ecd391c37ea677c56723e248732a2d Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 20 Aug 2018 11:35:26 +0300 Subject: [PATCH 05/76] Fixed player online status on server switch, reduced delay 20 -> 5s #646 --- .../listeners/bungee/PlayerOnlineListener.java | 4 ++-- .../processors/info/PlayerPageUpdateProcessor.java | 2 +- .../plan/system/cache/SessionCacheTest.java | 12 ++++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/listeners/bungee/PlayerOnlineListener.java b/Plan/src/main/java/com/djrapitops/plan/system/listeners/bungee/PlayerOnlineListener.java index efb286640..1ab692619 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/listeners/bungee/PlayerOnlineListener.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/listeners/bungee/PlayerOnlineListener.java @@ -15,8 +15,8 @@ import com.djrapitops.plan.system.webserver.cache.PageId; import com.djrapitops.plan.system.webserver.cache.ResponseCache; import com.djrapitops.plugin.api.utility.log.Log; import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PostLoginEvent; -import net.md_5.bungee.api.event.ServerDisconnectEvent; import net.md_5.bungee.api.event.ServerSwitchEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; @@ -53,7 +53,7 @@ public class PlayerOnlineListener implements Listener { } @EventHandler - public void onLogout(ServerDisconnectEvent event) { + public void onLogout(PlayerDisconnectEvent event) { try { ProxiedPlayer player = event.getPlayer(); UUID uuid = player.getUniqueId(); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/info/PlayerPageUpdateProcessor.java b/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/info/PlayerPageUpdateProcessor.java index 98d0cfaa7..e6cbe7cf7 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/info/PlayerPageUpdateProcessor.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/info/PlayerPageUpdateProcessor.java @@ -32,7 +32,7 @@ public class PlayerPageUpdateProcessor implements Runnable { cancel(); } } - }).runTaskLaterAsynchronously(TimeAmount.SECOND.ticks() * 20); + }).runTaskLaterAsynchronously(TimeAmount.SECOND.ticks() * 5); } } } diff --git a/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java b/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java index 50e45a227..65eda615b 100644 --- a/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java +++ b/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java @@ -43,4 +43,16 @@ public class SessionCacheTest { assertTrue(cachedSession.isPresent()); assertEquals(session, cachedSession.get()); } + + @Test + public void testBungeeReCaching() { + SessionCache cache = new BungeeDataCache(null); + cache.cacheSession(uuid, session); + Session expected = new Session(uuid, 0, "", ""); + cache.cacheSession(uuid, expected); + + Optional result = SessionCache.getCachedSession(uuid); + assertTrue(result.isPresent()); + assertEquals(expected, result.get()); + } } \ No newline at end of file From d587892df2d260522b0f188e92e2ef9bf724bf21 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 20 Aug 2018 11:44:21 +0300 Subject: [PATCH 06/76] Fixed tests --- .../plan/data/container/Session.java | 22 ++++++++++++------- .../plan/system/cache/SessionCacheTest.java | 10 +++++---- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java b/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java index 4a6fb62c4..7a83447c0 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java @@ -182,14 +182,8 @@ public class Session extends DataContainer implements DateHolder { getValue(SessionKeys.END).orElse(-1L).equals(session.getValue(SessionKeys.END).orElse(-1L)) && mobKills == session.mobKills && deaths == session.deaths && - Objects.equals( - getValue(SessionKeys.WORLD_TIMES).orElse(null), - session.getValue(SessionKeys.WORLD_TIMES).orElse(null) - ) && - Objects.equals( - getValue(SessionKeys.PLAYER_KILLS).orElse(new ArrayList<>()), - session.getValue(SessionKeys.PLAYER_KILLS).orElse(new ArrayList<>()) - ); + Objects.equals(playerKills, session.playerKills) && + Objects.equals(worldTimes, session.worldTimes); } @Override @@ -252,4 +246,16 @@ public class Session extends DataContainer implements DateHolder { return theWorld + " (" + Formatters.percentage().apply(quotient) + ")"; } + + @Override + public String toString() { + return "Session{" + + "sessionStart=" + sessionStart + + ", worldTimes=" + worldTimes + + ", playerKills=" + playerKills + + ", mobKills=" + mobKills + + ", deaths=" + deaths + + ", afkTime=" + afkTime + + '}'; + } } diff --git a/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java b/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java index 65eda615b..7316387ab 100644 --- a/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java +++ b/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java @@ -1,10 +1,7 @@ package com.djrapitops.plan.system.cache; import com.djrapitops.plan.data.container.Session; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; +import org.junit.*; import org.junit.rules.TemporaryFolder; import utilities.TestConstants; import utilities.mocks.SystemMockUtil; @@ -36,6 +33,11 @@ public class SessionCacheTest { sessionCache.cacheSession(uuid, session); } + @After + public void tearDown() { + SessionCache.clear(); + } + @Test public void testAtomity() { SessionCache reloaded = new SessionCache(null); From 268474d5caf6271681000a4286d485059c91c01d Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Tue, 21 Aug 2018 09:46:43 +0300 Subject: [PATCH 07/76] Added config settings for ping delays #691 --- .../java/com/djrapitops/plan/system/settings/Settings.java | 2 ++ .../plan/system/settings/network/NetworkSettings.java | 4 +++- .../com/djrapitops/plan/system/tasks/BukkitTaskSystem.java | 5 ++++- .../djrapitops/plan/system/tasks/server/PingCountTimer.java | 3 ++- Plan/src/main/resources/bungeeconfig.yml | 3 +++ Plan/src/main/resources/config.yml | 3 +++ 6 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/settings/Settings.java b/Plan/src/main/java/com/djrapitops/plan/system/settings/Settings.java index 3a8d7e63f..0b283d009 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/settings/Settings.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/settings/Settings.java @@ -52,6 +52,8 @@ public enum Settings { AFK_THRESHOLD_MINUTES("Data.AFKThresholdMinutes"), KEEP_LOGS_DAYS("Plugin.KeepLogsForXDays"), KEEP_INACTIVE_PLAYERS_DAYS("Data.KeepInactivePlayerDataForDays"), + PING_SERVER_ENABLE_DELAY("Data.Ping.ServerEnableDelaySeconds"), + PING_PLAYER_LOGIN_DELAY("Data.Ping.PlayerLoginDelaySeconds"), // String DEBUG("Plugin.Debug"), diff --git a/Plan/src/main/java/com/djrapitops/plan/system/settings/network/NetworkSettings.java b/Plan/src/main/java/com/djrapitops/plan/system/settings/network/NetworkSettings.java index 7bc6990ad..985df52c5 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/settings/network/NetworkSettings.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/settings/network/NetworkSettings.java @@ -174,7 +174,9 @@ public class NetworkSettings { AFK_THRESHOLD_MINUTES, DATA_GEOLOCATIONS, KEEP_LOGS_DAYS, - KEEP_INACTIVE_PLAYERS_DAYS + KEEP_INACTIVE_PLAYERS_DAYS, + PING_SERVER_ENABLE_DELAY, + PING_PLAYER_LOGIN_DELAY }; Log.debug("NetworkSettings: Adding Config Values.."); for (Settings setting : sameStrings) { diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java index dfe9bd493..508efa0d4 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java @@ -5,10 +5,12 @@ package com.djrapitops.plan.system.tasks; import com.djrapitops.plan.Plan; +import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.system.tasks.server.BukkitTPSCountTimer; import com.djrapitops.plan.system.tasks.server.PaperTPSCountTimer; import com.djrapitops.plan.system.tasks.server.PingCountTimer; import com.djrapitops.plugin.api.Check; +import com.djrapitops.plugin.api.TimeAmount; import com.djrapitops.plugin.task.RunnableFactory; import org.bukkit.Bukkit; @@ -33,8 +35,9 @@ public class BukkitTaskSystem extends ServerTaskSystem { try { PingCountTimer pingCountTimer = new PingCountTimer(); ((Plan) plugin).registerListener(pingCountTimer); + long startDelay = TimeAmount.SECOND.ms() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); RunnableFactory.createNew("PingCountTimer", pingCountTimer) - .runTaskTimer(20L, PingCountTimer.PING_INTERVAL); + .runTaskTimer(startDelay, PingCountTimer.PING_INTERVAL); } catch (ExceptionInInitializerError | NoClassDefFoundError ignore) { // Running CraftBukkit } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimer.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimer.java index 8e1e360c0..37c0b728b 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimer.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimer.java @@ -26,6 +26,7 @@ package com.djrapitops.plan.system.tasks.server; import com.djrapitops.plan.data.store.objects.DateObj; import com.djrapitops.plan.system.processing.Processing; import com.djrapitops.plan.system.processing.processors.player.PingInsertProcessor; +import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.utilities.java.Reflection; import com.djrapitops.plugin.api.TimeAmount; import com.djrapitops.plugin.api.utility.log.Log; @@ -160,7 +161,7 @@ public class PingCountTimer extends AbsRunnable implements Listener { addPlayer(player); } } - }).runTaskLater(TimeAmount.SECOND.ticks() * 15L); + }).runTaskLater(TimeAmount.SECOND.ticks() * (long) Settings.PING_PLAYER_LOGIN_DELAY.getNumber()); } @EventHandler diff --git a/Plan/src/main/resources/bungeeconfig.yml b/Plan/src/main/resources/bungeeconfig.yml index 0e4be559d..02d582993 100644 --- a/Plan/src/main/resources/bungeeconfig.yml +++ b/Plan/src/main/resources/bungeeconfig.yml @@ -67,6 +67,9 @@ Data: LogUnknownCommands: false CombineCommandAliases: true Geolocations: true + Ping: + ServerEnableDelaySeconds: 300 + PlayerLoginDelaySeconds: 30 KeepInactivePlayerDataForDays: 180 # ----------------------------------------------------- Customization: diff --git a/Plan/src/main/resources/config.yml b/Plan/src/main/resources/config.yml index be15fa519..266672566 100644 --- a/Plan/src/main/resources/config.yml +++ b/Plan/src/main/resources/config.yml @@ -81,6 +81,9 @@ Data: LogUnknownCommands: false CombineCommandAliases: true Geolocations: true + Ping: + ServerEnableDelaySeconds: 300 + PlayerLoginDelaySeconds: 30 KeepInactivePlayerDataForDays: 180 # ----------------------------------------------------- Customization: From 3afaad973124dda2365e9e98b4cbca39fe9af12f Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Tue, 21 Aug 2018 10:23:13 +0300 Subject: [PATCH 08/76] Changed gathered ping average to be median instead #691 (For each datapoint a median is calculated, which is then used in calculation for mean values, averages) --- .../player/PingInsertProcessor.java | 25 ++++-- .../plan/utilities/analysis/Median.java | 58 ++++++++++++ .../player/PingInsertProcessorTest.java | 66 ++++++++++++++ .../plan/utilities/analysis/MedianTest.java | 90 +++++++++++++++++++ 4 files changed, 230 insertions(+), 9 deletions(-) create mode 100644 Plan/src/main/java/com/djrapitops/plan/utilities/analysis/Median.java create mode 100644 Plan/src/test/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessorTest.java create mode 100644 Plan/src/test/java/com/djrapitops/plan/utilities/analysis/MedianTest.java diff --git a/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessor.java b/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessor.java index 0f068bfcf..abc1d6702 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessor.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessor.java @@ -9,10 +9,12 @@ import com.djrapitops.plan.data.store.objects.DateObj; import com.djrapitops.plan.system.database.databases.Database; import com.djrapitops.plan.system.info.server.ServerInfo; import com.djrapitops.plan.system.processing.CriticalRunnable; +import com.djrapitops.plan.utilities.analysis.Median; import java.util.List; import java.util.OptionalInt; import java.util.UUID; +import java.util.stream.Collectors; /** * Processes 60s values of a Ping list. @@ -44,23 +46,28 @@ public class PingInsertProcessor implements CriticalRunnable { return; } - int minValue = history.stream() - .mapToInt(DateObj::getValue) - .filter(i -> i > 0 && i < 4000) - .min().orElse(-1); + int minValue = getMinValue(history); - double avgValue = history.stream() - .mapToInt(DateObj::getValue) - .filter(i -> i > 0 && i < 4000) - .average().orElse(-1); + int meanValue = getMeanValue(history); int maxValue = max.getAsInt(); Ping ping = new Ping(lastDate, ServerInfo.getServerUUID(), minValue, maxValue, - avgValue); + meanValue); Database.getActive().save().ping(uuid, ping); } + + int getMinValue(List> history) { + return history.stream() + .mapToInt(DateObj::getValue) + .filter(i -> i > 0 && i < 4000) + .min().orElse(-1); + } + + int getMeanValue(List> history) { + return (int) Median.forInt(history.stream().map(DateObj::getValue).collect(Collectors.toList())).calculate(); + } } diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/analysis/Median.java b/Plan/src/main/java/com/djrapitops/plan/utilities/analysis/Median.java new file mode 100644 index 000000000..92abc49cc --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/analysis/Median.java @@ -0,0 +1,58 @@ +package com.djrapitops.plan.utilities.analysis; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Math utility for calculating the median from Integer values. + * + * @author Rsl1122 + */ +public class Median { + + private final List values; + private int size; + + private Median(Collection values, int b) { + this(values.stream().map(i -> (long) i).collect(Collectors.toList())); + } + + private Median(List values) { + this.values = values; + Collections.sort(values); + size = values.size(); + } + + public static Median forInt(Collection integers) { + return new Median(integers, 0); + } + + public static Median forLong(List longs) { + return new Median(longs); + } + + public double calculate() { + if (values.isEmpty()) { + return -1; + } + if (size % 2 == 0) { + return calculateEven(); + } else { + return calculateOdd(); + } + } + + private double calculateEven() { + int half = size / 2; + double x1 = values.get(half); + double x2 = values.get(half - 1); + return (x1 + x2) / 2; + } + + private double calculateOdd() { + int half = size / 2; + return (double) values.get(half); + } +} \ No newline at end of file diff --git a/Plan/src/test/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessorTest.java b/Plan/src/test/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessorTest.java new file mode 100644 index 000000000..ba7c40523 --- /dev/null +++ b/Plan/src/test/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessorTest.java @@ -0,0 +1,66 @@ +package com.djrapitops.plan.system.processing.processors.player; + +import com.djrapitops.plan.data.store.objects.DateObj; +import com.djrapitops.plan.utilities.analysis.Median; +import com.djrapitops.plugin.api.TimeAmount; +import org.junit.Before; +import org.junit.Test; +import utilities.RandomData; +import utilities.TestConstants; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; + +/** + * Test for {@link PingInsertProcessor}. + * + * @author Rsl1122 + */ +public class PingInsertProcessorTest { + + private List> testPing; + + @Before + public void setUp() { + testPing = new ArrayList<>(); + + for (int i = 0; i < TimeAmount.MINUTE.ms(); i += TimeAmount.SECOND.ms() * 2L) { + testPing.add(new DateObj<>(i, RandomData.randomInt(1, 4000))); + } + } + + @Test + public void testMedian() { + List collect = testPing.stream().map(DateObj::getValue).sorted().collect(Collectors.toList()); + System.out.println(collect); + int expected = (int) Median.forInt(collect).calculate(); + int result = new PingInsertProcessor(TestConstants.PLAYER_ONE_UUID, new ArrayList<>()).getMeanValue(testPing); + System.out.println(result); + + assertEquals(expected, result); + } + + @Test + public void testMedianSingleEntry() { + int expected = 50; + int result = new PingInsertProcessor(TestConstants.PLAYER_ONE_UUID, new ArrayList<>()).getMeanValue( + Collections.singletonList(new DateObj<>(0, expected)) + ); + + assertEquals(expected, result); + } + + @Test + public void testMedianEmpty() { + int expected = -1; + int result = new PingInsertProcessor(TestConstants.PLAYER_ONE_UUID, new ArrayList<>()).getMeanValue( + Collections.emptyList() + ); + + assertEquals(expected, result); + } +} \ No newline at end of file diff --git a/Plan/src/test/java/com/djrapitops/plan/utilities/analysis/MedianTest.java b/Plan/src/test/java/com/djrapitops/plan/utilities/analysis/MedianTest.java new file mode 100644 index 000000000..50a0a3656 --- /dev/null +++ b/Plan/src/test/java/com/djrapitops/plan/utilities/analysis/MedianTest.java @@ -0,0 +1,90 @@ +package com.djrapitops.plan.utilities.analysis; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link Median}. + * + * @author Rsl1122 + */ +public class MedianTest { + + @Test + public void simpleOdd() { + List testValues = Arrays.asList(1, 3, 3, 6, 7, 8, 9); + Collections.shuffle(testValues); + double expected = 6; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void simpleEven() { + List testValues = Arrays.asList(1, 2, 3, 4, 5, 6, 8, 9); + Collections.shuffle(testValues); + double expected = 4.5; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void empty() { + double expected = -1; + double result = Median.forInt(Collections.emptyList()).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void singleValue() { + double expected = 50; + double result = Median.forInt(Collections.singletonList((int) expected)).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void twoValues() { + List testValues = Arrays.asList(1, 2); + double expected = 1.5; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void overflowOdd() { + List testValues = Arrays.asList(Integer.MIN_VALUE, 2, Integer.MAX_VALUE); + double expected = 2; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void overflowEven() { + List testValues = Arrays.asList(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + double expected = -0.5; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void overflowEven2() { + List testValues = Arrays.asList(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + double expected = Integer.MAX_VALUE; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + +} \ No newline at end of file From bae8708e87b29739ce39b3bbeee00a1f3052e3d9 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Tue, 21 Aug 2018 10:24:38 +0300 Subject: [PATCH 09/76] Improved Session Length Median calculation on even number of sessions --- .../plan/data/store/mutators/SessionsMutator.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/SessionsMutator.java b/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/SessionsMutator.java index 10be83942..203d1c91a 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/SessionsMutator.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/SessionsMutator.java @@ -7,6 +7,7 @@ import com.djrapitops.plan.data.store.containers.DataContainer; import com.djrapitops.plan.data.store.keys.CommonKeys; import com.djrapitops.plan.data.store.keys.SessionKeys; import com.djrapitops.plan.data.time.WorldTimes; +import com.djrapitops.plan.utilities.analysis.Median; import java.util.*; import java.util.function.Predicate; @@ -142,13 +143,8 @@ public class SessionsMutator { } public long toMedianSessionLength() { - List sessionLengths = sessions.stream().map(Session::getLength) - .sorted() - .collect(Collectors.toList()); - if (sessionLengths.isEmpty()) { - return 0; - } - return sessionLengths.get(sessionLengths.size() / 2); + List sessionLengths = sessions.stream().map(Session::getLength).collect(Collectors.toList()); + return (long) Median.forLong(sessionLengths).calculate(); } public int toAverageUniqueJoinsPerDay() { @@ -200,5 +196,4 @@ public class SessionsMutator { return toPlayerDeathList().size(); } - } \ No newline at end of file From f5cc33a7173ce2bcde0150a15c19114507647f78 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Tue, 21 Aug 2018 15:17:59 +0300 Subject: [PATCH 10/76] Add /planbungee uninstalled --- .../java/com/djrapitops/plan/command/PlanBungeeCommand.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Plan/src/main/java/com/djrapitops/plan/command/PlanBungeeCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/PlanBungeeCommand.java index f7daef2ea..7e37fd8f3 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/PlanBungeeCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/PlanBungeeCommand.java @@ -4,6 +4,7 @@ import com.djrapitops.plan.PlanPlugin; import com.djrapitops.plan.command.commands.*; import com.djrapitops.plan.command.commands.manage.ManageConDebugCommand; import com.djrapitops.plan.command.commands.manage.ManageRawDataCommand; +import com.djrapitops.plan.command.commands.manage.ManageUninstalledCommand; import com.djrapitops.plan.system.locale.Locale; import com.djrapitops.plan.system.locale.lang.DeepHelpLang; import com.djrapitops.plan.system.settings.Permissions; @@ -44,6 +45,7 @@ public class PlanBungeeCommand extends TreeCmdNode { new ManageConDebugCommand(plugin), new ManageRawDataCommand(plugin), new BungeeSetupToggleCommand(plugin), + new ManageUninstalledCommand(plugin), new ReloadCommand(plugin), new DisableCommand(plugin), new StatusCommand<>(plugin, Permissions.MANAGE.getPermission(), plugin.getColorScheme()), From 8fb54ed905a725aac419419fe8f90c572503c958 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Tue, 21 Aug 2018 16:56:34 +0300 Subject: [PATCH 11/76] Added Connection: close header to HttpClient in ConnectionOut --- .../djrapitops/plan/system/info/connection/ConnectionOut.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/info/connection/ConnectionOut.java b/Plan/src/main/java/com/djrapitops/plan/system/info/connection/ConnectionOut.java index ca2b9fbe3..15881c207 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/info/connection/ConnectionOut.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/info/connection/ConnectionOut.java @@ -144,6 +144,7 @@ public class ConnectionOut { post.setHeader("Content-Type", "application/x-www-form-urlencoded"); post.setHeader("charset", "UTF-8"); + post.setHeader("Connection", "close"); byte[] toSend = parameters.getBytes(); post.setEntity(new ByteArrayEntity(toSend)); From 8ca16a1aa99737a36f6b3aa477996d94c4681f56 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Wed, 22 Aug 2018 10:38:44 +0300 Subject: [PATCH 12/76] Fixed Patch Task causing issues on disable #695 --- .../database/databases/sql/PatchTask.java | 56 +++++++++++++++++++ .../system/database/databases/sql/SQLDB.java | 32 ++--------- 2 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/PatchTask.java diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/PatchTask.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/PatchTask.java new file mode 100644 index 000000000..3a5f79711 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/PatchTask.java @@ -0,0 +1,56 @@ +package com.djrapitops.plan.system.database.databases.sql; + +import com.djrapitops.plan.PlanPlugin; +import com.djrapitops.plan.system.database.databases.sql.patches.Patch; +import com.djrapitops.plan.system.locale.Locale; +import com.djrapitops.plan.system.locale.lang.PluginLang; +import com.djrapitops.plugin.api.utility.log.Log; +import com.djrapitops.plugin.task.AbsRunnable; + +import java.util.function.Supplier; + +/** + * Task that is in charge on patching the database when the database enables. + * + * @author Rsl1122 + */ +public class PatchTask extends AbsRunnable { + + private final Patch[] patches; + private final Supplier locale; + + public PatchTask(Patch[] patches, Supplier locale) { + this.patches = patches; + this.locale = locale; + } + + @Override + public void run() { + try { + boolean didApply = applyPatches(); + Log.info(locale.get().getString( + didApply ? PluginLang.DB_APPLIED_PATCHES : PluginLang.DB_APPLIED_PATCHES_ALREADY + )); + } catch (Exception e) { + Log.error("----------------------------------------------------"); + Log.error(locale.get().getString(PluginLang.ENABLE_FAIL_DB_PATCH)); + Log.error("----------------------------------------------------"); + Log.toLog(this.getClass(), e); + PlanPlugin.getInstance().onDisable(); + } + } + + private boolean applyPatches() { + boolean didApply = false; + for (Patch patch : patches) { + if (!patch.hasBeenApplied()) { + String patchName = patch.getClass().getSimpleName(); + Log.info(locale.get().getString(PluginLang.DB_APPLY_PATCH, patchName)); + patch.apply(); + didApply = true; + } + } + return didApply; + } + +} \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/SQLDB.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/SQLDB.java index 2e1656591..46b376125 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/SQLDB.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/SQLDB.java @@ -1,6 +1,5 @@ package com.djrapitops.plan.system.database.databases.sql; -import com.djrapitops.plan.PlanPlugin; import com.djrapitops.plan.api.exceptions.database.DBInitException; import com.djrapitops.plan.api.exceptions.database.DBOpException; import com.djrapitops.plan.system.database.databases.Database; @@ -157,31 +156,12 @@ public abstract class SQLDB extends Database { new VersionTableRemovalPatch(this) }; - RunnableFactory.createNew("Database Patch", new AbsRunnable() { - @Override - public void run() { - try { - boolean applied = false; - for (Patch patch : patches) { - if (!patch.hasBeenApplied()) { - String patchName = patch.getClass().getSimpleName(); - Log.info(locale.get().getString(PluginLang.DB_APPLY_PATCH, patchName)); - patch.apply(); - applied = true; - } - } - Log.info(locale.get().getString( - applied ? PluginLang.DB_APPLIED_PATCHES : PluginLang.DB_APPLIED_PATCHES_ALREADY - )); - } catch (Exception e) { - Log.error("----------------------------------------------------"); - Log.error(locale.get().getString(PluginLang.ENABLE_FAIL_DB_PATCH)); - Log.error("----------------------------------------------------"); - Log.toLog(this.getClass(), e); - PlanPlugin.getInstance().onDisable(); - } - } - }).runTaskLaterAsynchronously(TimeAmount.SECOND.ticks() * 5L); + try { + RunnableFactory.createNew("Database Patch", new PatchTask(patches, locale)) + .runTaskLaterAsynchronously(TimeAmount.SECOND.ticks() * 5L); + } catch (Exception ignore) { + // Task failed to register because plugin is being disabled + } } catch (DBOpException e) { throw new DBInitException("Failed to set-up Database", e); } From 710779e7b08d194fd693cfeaebc0e9bd91f46503 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Wed, 22 Aug 2018 10:39:32 +0300 Subject: [PATCH 13/76] Fixed Ping Enable delay #694 --- .../java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java index 508efa0d4..b99dde0c8 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java @@ -35,7 +35,7 @@ public class BukkitTaskSystem extends ServerTaskSystem { try { PingCountTimer pingCountTimer = new PingCountTimer(); ((Plan) plugin).registerListener(pingCountTimer); - long startDelay = TimeAmount.SECOND.ms() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); + long startDelay = TimeAmount.SECOND.ticks() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); RunnableFactory.createNew("PingCountTimer", pingCountTimer) .runTaskTimer(startDelay, PingCountTimer.PING_INTERVAL); } catch (ExceptionInInitializerError | NoClassDefFoundError ignore) { From 5143e0fd7fd0ae4a0d5b0e7746e06ba19ac924af Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Wed, 22 Aug 2018 10:45:15 +0300 Subject: [PATCH 14/76] Reduced AFK perm check to a single check until next reload #693 --- .../plan/system/listeners/bukkit/AFKListener.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/AFKListener.java b/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/AFKListener.java index 2b7f51409..6f1246cc6 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/AFKListener.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/AFKListener.java @@ -12,6 +12,8 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerEvent; import org.bukkit.event.player.PlayerMoveEvent; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; /** @@ -27,14 +29,24 @@ public class AFKListener implements Listener { // Static so that /reload does not cause afk tracking to fail. public static final AFKTracker AFK_TRACKER = new AFKTracker(); + private final Map ignorePermissionInfo; + + public AFKListener() { + ignorePermissionInfo = new HashMap<>(); + } + private void event(PlayerEvent event) { try { Player player = event.getPlayer(); UUID uuid = player.getUniqueId(); long time = System.currentTimeMillis(); - if (player.hasPermission(Permissions.IGNORE_AFK.getPermission())) { + boolean ignored = ignorePermissionInfo.getOrDefault(uuid, player.hasPermission(Permissions.IGNORE_AFK.getPermission())); + if (ignored) { AFK_TRACKER.hasIgnorePermission(uuid); + ignorePermissionInfo.put(uuid, true); + } else { + ignorePermissionInfo.put(uuid, false); } AFK_TRACKER.performedAction(uuid, time); From d42178b8cdc40040982a99d09a05f8a7f45dc024 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Wed, 22 Aug 2018 10:52:05 +0300 Subject: [PATCH 15/76] Update versions.txt --- versions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/versions.txt b/versions.txt index 26ed37777..f2995a381 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,4 @@ +DEV|4.4.3-b3|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.4-DEV3/Plan-4.4.3-b3.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.4-DEV3 REL|4.4.3|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.3/Plan-4.4.3.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=238545 REL|4.4.2|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.2/Plan-4.4.2.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=237951 REL|4.4.1|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.1/Plan-4.4.1.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=234420 From f6737969a31a4310985eafe6318b26a9219acf0e Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 23 Aug 2018 08:40:44 +0300 Subject: [PATCH 16/76] Delete ISSUE_TEMPLATE --- .github/ISSUE_TEMPLATE | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE deleted file mode 100644 index 9545ede0b..000000000 --- a/.github/ISSUE_TEMPLATE +++ /dev/null @@ -1,11 +0,0 @@ -Instructions for issue reporting: -- Write description of the issue -- Copy possible errors here, If you're using 4.0.7+ head to `
/debug` & copy the information to the ticket. - -For suggestions use any format you want. :) - ----- - -**Description of the issue** - - From 9bc872a0702f42fd2943171c4c2ed548cc8d3014 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 23 Aug 2018 08:53:54 +0300 Subject: [PATCH 17/76] Update issue templates (#696) * Create bug_report.md * Create feature_request.md --- .github/ISSUE_TEMPLATE/bug_report.md | 28 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 16 +++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..299526883 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Create a report to help improving the plugin + +--- + +### Describe the bug + + + + +### Server information + + + + +### Exceptions & Other Logs + + + + + +### Additional information + + +**Frequency of occurrence**: <> + +**Other:** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..e1788905f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Suggestion +about: Feature request or an addition +--- + +### Is your feature request related to a problem? Please describe. + + +### Describe the solution you'd like + + +### Describe alternatives you've considered + + +### Additional context + From 6585fa18a147682dffb79ba189dce26be3e1fb36 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 23 Aug 2018 18:56:25 +0300 Subject: [PATCH 18/76] Fixed current session not being displayed on Analysis page #697 --- .../database/databases/sql/operation/SQLFetchOps.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java index e73dd6de5..30e957451 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java @@ -9,9 +9,11 @@ import com.djrapitops.plan.data.store.mutators.PlayersMutator; import com.djrapitops.plan.data.store.mutators.SessionsMutator; import com.djrapitops.plan.data.store.objects.DateObj; import com.djrapitops.plan.data.time.WorldTimes; +import com.djrapitops.plan.system.cache.SessionCache; import com.djrapitops.plan.system.database.databases.operation.FetchOperations; import com.djrapitops.plan.system.database.databases.sql.SQLDB; import com.djrapitops.plan.system.info.server.Server; +import com.djrapitops.plan.system.info.server.ServerInfo; import com.djrapitops.plugin.api.TimeAmount; import java.util.*; @@ -84,7 +86,13 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { // Calculating getters container.putSupplier(ServerKeys.OPERATORS, () -> PlayersMutator.forContainer(container).operators()); - container.putSupplier(ServerKeys.SESSIONS, () -> PlayersMutator.forContainer(container).getSessions()); + container.putSupplier(ServerKeys.SESSIONS, () -> { + List sessions = PlayersMutator.forContainer(container).getSessions(); + if (serverUUID.equals(ServerInfo.getServerUUID())) { + sessions.addAll(SessionCache.getActiveSessions().values()); + } + return sessions; + }); container.putSupplier(ServerKeys.PLAYER_KILLS, () -> SessionsMutator.forContainer(container).toPlayerKillList()); container.putSupplier(ServerKeys.PLAYER_KILL_COUNT, () -> container.getUnsafe(ServerKeys.PLAYER_KILLS).size()); container.putSupplier(ServerKeys.MOB_KILL_COUNT, () -> SessionsMutator.forContainer(container).toMobKillCount()); From e75f9a1027f328a06bbef781da3ca32c64de0725 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 23 Aug 2018 18:59:05 +0300 Subject: [PATCH 19/76] Version bump to 4.4.4 --- Plan/src/main/java/com/djrapitops/plan/PlanSponge.java | 2 +- Plan/src/main/resources/bungee.yml | 2 +- Plan/src/main/resources/plugin.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java b/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java index a88e42f62..60ece7eba 100644 --- a/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java +++ b/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java @@ -25,7 +25,7 @@ import org.spongepowered.api.plugin.Plugin; import java.io.File; import java.io.InputStream; -@Plugin(id = "plan", name = "Plan", version = "4.4.3", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"}) +@Plugin(id = "plan", name = "Plan", version = "4.4.4", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"}) public class PlanSponge extends SpongePlugin implements PlanPlugin { @Inject diff --git a/Plan/src/main/resources/bungee.yml b/Plan/src/main/resources/bungee.yml index 3d5049815..365f2b64d 100644 --- a/Plan/src/main/resources/bungee.yml +++ b/Plan/src/main/resources/bungee.yml @@ -1,4 +1,4 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.PlanBungee -version: 4.4.3 \ No newline at end of file +version: 4.4.4 \ No newline at end of file diff --git a/Plan/src/main/resources/plugin.yml b/Plan/src/main/resources/plugin.yml index 196fb25b3..8583e4134 100644 --- a/Plan/src/main/resources/plugin.yml +++ b/Plan/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.Plan -version: 4.4.3 +version: 4.4.4 softdepend: - EssentialsX - Towny From a130ce97ad72c5b1ae6c95c40b9e32ebac538d64 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 23 Aug 2018 19:07:46 +0300 Subject: [PATCH 20/76] Fixed Session PlayerKills not being saved --- .../main/java/com/djrapitops/plan/data/container/Session.java | 2 +- .../plan/system/database/databases/sql/tables/KillsTable.java | 4 ++-- .../system/database/databases/sql/tables/SessionsTable.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java b/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java index 7a83447c0..704ad2346 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/container/Session.java @@ -199,7 +199,7 @@ public class Session extends DataContainer implements DateHolder { return worldTimes; } - private List getPlayerKills() { + public List getPlayerKills() { return playerKills; } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/KillsTable.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/KillsTable.java index f7ee98ee6..a48ed9e62 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/KillsTable.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/KillsTable.java @@ -117,7 +117,7 @@ public class KillsTable extends UserIDTable { UUID victim = UUID.fromString(uuidS); long date = set.getLong(Col.DATE.get()); String weapon = set.getString(Col.WEAPON.get()); - session.getUnsafe(SessionKeys.PLAYER_KILLS).add(new PlayerKill(victim, weapon, date)); + session.getPlayerKills().add(new PlayerKill(victim, weapon, date)); } return null; } @@ -286,7 +286,7 @@ public class KillsTable extends UserIDTable { for (Session session : sessions) { int sessionID = session.getUnsafe(SessionKeys.DB_ID); // Every kill - for (PlayerKill kill : session.getUnsafe(SessionKeys.PLAYER_KILLS)) { + for (PlayerKill kill : session.getPlayerKills()) { UUID victim = kill.getVictim(); long date = kill.getDate(); String weapon = kill.getWeapon(); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/SessionsTable.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/SessionsTable.java index 7aae3de6a..5730b55e7 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/SessionsTable.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/SessionsTable.java @@ -113,7 +113,7 @@ public class SessionsTable extends UserIDTable { } db.getWorldTimesTable().saveWorldTimes(uuid, sessionID, session.getUnsafe(SessionKeys.WORLD_TIMES)); - db.getKillsTable().savePlayerKills(uuid, sessionID, session.getUnsafe(SessionKeys.PLAYER_KILLS)); + db.getKillsTable().savePlayerKills(uuid, sessionID, session.getPlayerKills()); } /** From 13eec014b32bdf7184d9ed087270967f3e827684 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 23 Aug 2018 19:30:16 +0300 Subject: [PATCH 21/76] Update versions.txt --- versions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/versions.txt b/versions.txt index f2995a381..08f3ef568 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,4 @@ +REL|4.4.4|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.4-REL/Plan-4.4.4.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=240584 DEV|4.4.3-b3|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.4-DEV3/Plan-4.4.3-b3.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.4-DEV3 REL|4.4.3|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.3/Plan-4.4.3.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=238545 REL|4.4.2|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.2/Plan-4.4.2.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=237951 From 41d39ac23481acbe919e895a2f7842c95458013c Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sun, 26 Aug 2018 10:02:41 +0300 Subject: [PATCH 22/76] Removed config tab from debug page --- .../plan/utilities/html/pages/DebugPage.java | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/html/pages/DebugPage.java b/Plan/src/main/java/com/djrapitops/plan/utilities/html/pages/DebugPage.java index ad8af1497..aaf0c69e2 100644 --- a/Plan/src/main/java/com/djrapitops/plan/utilities/html/pages/DebugPage.java +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/html/pages/DebugPage.java @@ -14,7 +14,6 @@ import com.djrapitops.plan.system.info.server.Server; import com.djrapitops.plan.system.info.server.ServerInfo; import com.djrapitops.plan.system.info.server.properties.ServerProperties; import com.djrapitops.plan.system.webserver.cache.ResponseCache; -import com.djrapitops.plan.utilities.file.FileUtil; import com.djrapitops.plan.utilities.html.Html; import com.djrapitops.plan.utilities.html.HtmlStructure; import com.djrapitops.plan.utilities.html.icon.Icon; @@ -23,11 +22,9 @@ import com.djrapitops.plugin.api.Benchmark; import com.djrapitops.plugin.api.utility.log.ErrorLogger; import com.djrapitops.plugin.api.utility.log.Log; -import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; -import java.nio.charset.Charset; import java.util.*; /** @@ -52,10 +49,9 @@ public class DebugPage implements Page { TabsElement.Tab info = new TabsElement.Tab(Icon.called("server") + " Server Information", createServerInfoContent()); TabsElement.Tab errors = new TabsElement.Tab(Icon.called("exclamation-circle") + " Errors", createErrorContent()); TabsElement.Tab debugLog = new TabsElement.Tab(Icon.called("bug") + " Debug Log", createDebugLogContent()); - TabsElement.Tab config = new TabsElement.Tab(Icon.called("cogs") + " Plan Config", createConfigContent()); TabsElement.Tab caches = new TabsElement.Tab(Icon.called("archive") + " Plan Caches", createCacheContent()); - TabsElement tabs = new TabsElement(info, errors, debugLog, config, caches); + TabsElement tabs = new TabsElement(info, errors, debugLog, caches); return preContent + tabs.toHtmlFull(); } @@ -105,12 +101,6 @@ public class DebugPage implements Page { } } - private String createConfigContent() { - StringBuilder content = new StringBuilder(); - appendConfig(content); - return content.toString(); - } - private String createDebugLogContent() { StringBuilder content = new StringBuilder(); appendDebugLog(content); @@ -221,21 +211,6 @@ public class DebugPage implements Page { content.append(""); } - private void appendConfig(StringBuilder content) { - try { - File configFile = new File(PlanPlugin.getInstance().getDataFolder(), "config.yml"); - if (configFile.exists()) { - content.append("
### config.yml
```
"); - FileUtil.lines(configFile, Charset.forName("UTF-8")) - .stream().filter(line -> !line.toLowerCase().contains("pass") && !line.toLowerCase().contains("secret")) - .forEach(line -> content.append(line).append("
")); - content.append("```
"); - } - } catch (IOException e) { - Log.toLog(this.getClass(), e); - } - } - private void appendBenchmarks(StringBuilder content) { content.append("
### Benchmarks
```
"); try { From 5295d56f3c9d0d6b3593bbd801a9a8027dd533ad Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sun, 26 Aug 2018 18:15:51 +0300 Subject: [PATCH 23/76] Updated PlanPluginBridge dependencies to latest versions --- PlanPluginBridge/pom.xml | 205 +++++++----------- .../plan/react/ReactDataTask.java | 2 +- 2 files changed, 83 insertions(+), 124 deletions(-) diff --git a/PlanPluginBridge/pom.xml b/PlanPluginBridge/pom.xml index cc4e6edba..ad44b12b0 100644 --- a/PlanPluginBridge/pom.xml +++ b/PlanPluginBridge/pom.xml @@ -54,7 +54,7 @@ http://nexus.hc.to/content/repositories/pub_releases - jitpack.io + jitpack.io (GriefPrevention) https://jitpack.io @@ -82,14 +82,14 @@ 4.3.0-SNAPSHOT provided - + + com.destroystokyo.paper paper-api 1.12.2-R0.1-SNAPSHOT provided - org.spigotmc spigot-api @@ -97,6 +97,9 @@ jar provided + + + com.hm.achievement advanced-achievements-api @@ -104,39 +107,21 @@ provided - com.earth2me - essentials - 2.0.1 + com.github.TechFortress + GriefPrevention + 16.8 provided - com.massivecraft - factions - 2.10.0 + me.clip + placeholderapi + 2.9.1 provided - com.gamingmesh - jobs - 4.0.1 - provided - - - com.massivecraft - mcore - 2.10.0 - provided - - - com.gmail.nossr50 - mcMMO - 1.5.07 - provided - - - com.palmergames - towny - 0.91.4.0 + br.net.fabiozumbi12.RedProtect + RedProtect-Spigot + 7.5.3 provided @@ -146,18 +131,56 @@ provided - com.wasteofplastic.askyblock - ASkyBlock - 3.0.6.8 + us.myles + viaversion + 1.5.0 + provided + + + + + + me.konsolas + AAC + 3.5.0 provided - com.github.TechFortress - GriefPrevention - 16.7.1 + me.leoko + advancedban + 2.1.5 + provided + + + com.wasteofplastic.askyblock + ASkyBlock + 3.0.9.4 + provided + + + me.confuser + banmanager + 5.15.0 + provided + + + net.ess3 + EssentialsX + 2.15.0.1 + provided + + + com.massivecraft + factions + 2.14.0 + provided + + + com.massivecraft + mcore + 2.14.0 provided - net.kaikk.mc GriefPreventionPlus @@ -165,21 +188,9 @@ provided - litebans - api - 0.2 - provided - - - io.minimum - minecraft.superbvote - 0.4.1 - provided - - - us.myles - viaversion - 1.1.1 + com.gamingmesh + jobs + 4.7.4 provided @@ -189,51 +200,39 @@ provided - me.confuser - banmanager - 5.15.0 + litebans + api + 0.3 + provided + + + com.gmail.nossr50 + mcMMO + 1.6.0 provided - - - - - - com.github.ProtocolSupport ProtocolSupport 4.28 provided - - br.net.fabiozumbi12.RedProtect - RedProtect-Spigot - RELEASE - provided - - - me.konsolas - AAC - 3.3.5 - provided - - - me.clip - placeholderapi - LATEST - provided - com.volmit react - 6.549 + 6.573 provided - me.leoko - advancedban - 2.1.5 + io.minimum + minecraft.superbvote + 0.5.3 + provided + + + com.palmergames + towny + 0.92.0.0 provided @@ -245,46 +244,6 @@ maven-install-plugin 2.5.2 - org.jacoco jacoco-maven-plugin diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/react/ReactDataTask.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/react/ReactDataTask.java index 7510e0c88..2a44728a2 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/react/ReactDataTask.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/react/ReactDataTask.java @@ -7,8 +7,8 @@ import com.djrapitops.plugin.task.AbsRunnable; import com.volmit.react.React; import com.volmit.react.api.GraphSampleLine; import com.volmit.react.api.SampledType; -import com.volmit.react.util.GMap; import com.volmit.react.util.M; +import com.volmit.react.volume.lang.collections.GMap; import java.util.ArrayList; import java.util.EnumMap; From af2eed4d88228832355a0e49a315211424e3d8ed Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 27 Aug 2018 10:33:21 +0300 Subject: [PATCH 24/76] Added Bungee PluginData rendering support #571 Added Bungee Plugin Support: AdvancedBan, BuyCraft, LiteBans, ViaVersion Added Sponge Plugin support: BuyCraft --- Plan/dependency-reduced-pom.xml | 38 +++++----- .../store/containers/NetworkContainer.java | 3 + .../plan/system/info/BungeeInfoSystem.java | 7 +- .../connection/BungeeConnectionSystem.java | 9 ++- .../utilities/html/pages/NetworkPage.java | 8 +++ Plan/src/main/resources/bungeeconfig.yml | 7 +- Plan/src/main/resources/web/network.html | 10 +++ PlanPluginBridge/pom.xml | 12 +++- .../djrapitops/pluginbridge/plan/Bridge.java | 31 ++++++--- ...eBansHook.java => LiteBansBukkitHook.java} | 8 ++- .../plan/litebans/LiteBansBungeeHook.java | 69 +++++++++++++++++++ .../litebans/LiteBansDatabaseQueries.java | 4 +- ....java => BukkitPlayerVersionListener.java} | 4 +- .../BungeePlayerVersionListener.java | 46 +++++++++++++ .../plan/viaversion/Protocol.java | 2 + ...ionHook.java => ViaVersionBukkitHook.java} | 6 +- .../plan/viaversion/ViaVersionBungeeHook.java | 48 +++++++++++++ 17 files changed, 269 insertions(+), 43 deletions(-) rename PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/{LiteBansHook.java => LiteBansBukkitHook.java} (83%) create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansBungeeHook.java rename PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/{PlayerVersionListener.java => BukkitPlayerVersionListener.java} (92%) create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/BungeePlayerVersionListener.java rename PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/{ViaVersionHook.java => ViaVersionBukkitHook.java} (89%) create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/ViaVersionBungeeHook.java diff --git a/Plan/dependency-reduced-pom.xml b/Plan/dependency-reduced-pom.xml index f5d762d20..a8ec7baba 100644 --- a/Plan/dependency-reduced-pom.xml +++ b/Plan/dependency-reduced-pom.xml @@ -87,10 +87,10 @@ org.slf4j.Logger - - org.bstats - com.djrapitops.plan.utilities.metrics - + + org.bstats + com.djrapitops.plan.utilities.metrics + @@ -149,14 +149,14 @@ sponge-repo https://repo.spongepowered.org/maven - - md_5-snapshots - http://repo.md-5.net/content/repositories/snapshots/ - - - bstats-repo - http://repo.bstats.org/content/repositories/releases/ - + + md_5-snapshots + http://repo.md-5.net/content/repositories/snapshots/ + + + bstats-repo + http://repo.bstats.org/content/repositories/releases/ + @@ -359,12 +359,12 @@ - - com.imaginarycode.minecraft - RedisBungee - 0.3.8-SNAPSHOT - provided - + + com.imaginarycode.minecraft + RedisBungee + 0.3.8-SNAPSHOT + provided + org.mockito mockito-core @@ -388,7 +388,7 @@ org.xerial sqlite-jdbc - 3.23.1 + 3.23.1 test diff --git a/Plan/src/main/java/com/djrapitops/plan/data/store/containers/NetworkContainer.java b/Plan/src/main/java/com/djrapitops/plan/data/store/containers/NetworkContainer.java index 12b14fc28..dab7aae04 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/store/containers/NetworkContainer.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/store/containers/NetworkContainer.java @@ -173,4 +173,7 @@ public class NetworkContainer extends DataContainer { putSupplier(NetworkKeys.PLAYERS_MONTH, () -> getUnsafe(uniqueMonth).count()); } + public ServerContainer getBungeeContainer() { + return bungeeContainer; + } } \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/system/info/BungeeInfoSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/info/BungeeInfoSystem.java index c9e5e1d56..527597f46 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/info/BungeeInfoSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/info/BungeeInfoSystem.java @@ -10,6 +10,7 @@ import com.djrapitops.plan.api.exceptions.connection.WebException; import com.djrapitops.plan.system.info.connection.BungeeConnectionSystem; import com.djrapitops.plan.system.info.request.CacheRequest; import com.djrapitops.plan.system.info.request.GenerateInspectPageRequest; +import com.djrapitops.plan.system.info.request.GenerateInspectPluginsTabRequest; import com.djrapitops.plan.system.info.request.InfoRequest; import com.djrapitops.plan.system.info.server.ServerInfo; import com.djrapitops.plan.system.webserver.cache.PageId; @@ -30,8 +31,10 @@ public class BungeeInfoSystem extends InfoSystem { @Override public void runLocally(InfoRequest infoRequest) throws WebException { - if (infoRequest instanceof CacheRequest || - infoRequest instanceof GenerateInspectPageRequest) { + if (infoRequest instanceof CacheRequest + || infoRequest instanceof GenerateInspectPageRequest + || infoRequest instanceof GenerateInspectPluginsTabRequest + ) { infoRequest.runLocally(); } else { // runLocally is called when ConnectionSystem has no servers. diff --git a/Plan/src/main/java/com/djrapitops/plan/system/info/connection/BungeeConnectionSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/info/connection/BungeeConnectionSystem.java index 96aa03587..ae9516525 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/info/connection/BungeeConnectionSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/info/connection/BungeeConnectionSystem.java @@ -7,6 +7,7 @@ package com.djrapitops.plan.system.info.connection; import com.djrapitops.plan.api.exceptions.connection.NoServersException; import com.djrapitops.plan.api.exceptions.database.DBOpException; import com.djrapitops.plan.system.database.databases.Database; +import com.djrapitops.plan.system.info.InfoSystem; import com.djrapitops.plan.system.info.request.*; import com.djrapitops.plan.system.info.server.Server; import com.djrapitops.plan.system.info.server.ServerInfo; @@ -44,7 +45,9 @@ public class BungeeConnectionSystem extends ConnectionSystem { protected Server selectServerForRequest(InfoRequest infoRequest) throws NoServersException { refreshServerMap(); Server server = null; - if (infoRequest instanceof CacheRequest || infoRequest instanceof GenerateInspectPageRequest) { + if (infoRequest instanceof CacheRequest + || infoRequest instanceof GenerateInspectPageRequest + || infoRequest instanceof GenerateInspectPluginsTabRequest) { // Run locally return ServerInfo.getServer(); } else if (infoRequest instanceof GenerateAnalysisPageRequest) { @@ -66,6 +69,10 @@ public class BungeeConnectionSystem extends ConnectionSystem { for (Server server : bukkitServers.values()) { WebExceptionLogger.logIfOccurs(this.getClass(), () -> sendInfoRequest(infoRequest, server)); } + // Quick hack + if (infoRequest instanceof GenerateInspectPluginsTabRequest) { + WebExceptionLogger.logIfOccurs(this.getClass(), () -> InfoSystem.getInstance().sendRequest(infoRequest)); + } } @Override diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/html/pages/NetworkPage.java b/Plan/src/main/java/com/djrapitops/plan/utilities/html/pages/NetworkPage.java index 16175b197..073cafbd0 100644 --- a/Plan/src/main/java/com/djrapitops/plan/utilities/html/pages/NetworkPage.java +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/html/pages/NetworkPage.java @@ -13,6 +13,7 @@ import com.djrapitops.plan.system.webserver.cache.PageId; import com.djrapitops.plan.system.webserver.cache.ResponseCache; import com.djrapitops.plan.system.webserver.response.pages.parts.NetworkPageContent; import com.djrapitops.plan.utilities.file.FileUtil; +import com.djrapitops.plan.utilities.html.structure.AnalysisPluginsTabContentCreator; import static com.djrapitops.plan.data.store.keys.NetworkKeys.*; @@ -51,6 +52,13 @@ public class NetworkPage implements Page { ResponseCache.loadResponse(PageId.NETWORK_CONTENT.id(), NetworkPageContent::new); placeholderReplacer.put("tabContentServers", networkPageContent.getContents()); + String[] content = AnalysisPluginsTabContentCreator.createContent(networkContainer.getUnsafe(NetworkKeys.PLAYERS_MUTATOR), null); + String nav = content[0]; + String tabs = content[1]; + + placeholderReplacer.put("navPluginsTabs", nav); + placeholderReplacer.put("tabsPlugins", tabs); + return placeholderReplacer.apply(FileUtil.getStringFromResource("web/network.html")); } catch (Exception e) { throw new ParseException(e); diff --git a/Plan/src/main/resources/bungeeconfig.yml b/Plan/src/main/resources/bungeeconfig.yml index 02d582993..0a659ed81 100644 --- a/Plan/src/main/resources/bungeeconfig.yml +++ b/Plan/src/main/resources/bungeeconfig.yml @@ -148,4 +148,9 @@ Servers: Example: WebServerPort: 8034 ServerName: Example - ThemeBase: Default \ No newline at end of file + ThemeBase: Default +# ----------------------------------------------------- +Plugins: + BuyCraft: + # http://help.buycraft.net/article/36-where-to-find-the-secret-key + Secret: "-" \ No newline at end of file diff --git a/Plan/src/main/resources/web/network.html b/Plan/src/main/resources/web/network.html index 77dfe6b25..869b50233 100644 --- a/Plan/src/main/resources/web/network.html +++ b/Plan/src/main/resources/web/network.html @@ -130,6 +130,15 @@ Network Players +
  • + + extension + Plugins + +
      + ${navPluginsTabs} +
    +
  • @@ -487,6 +496,7 @@ + ${tabsPlugins} diff --git a/PlanPluginBridge/pom.xml b/PlanPluginBridge/pom.xml index ad44b12b0..eb7c024dc 100644 --- a/PlanPluginBridge/pom.xml +++ b/PlanPluginBridge/pom.xml @@ -41,6 +41,10 @@ + + bungeecord-repo + https://oss.sonatype.org/content/repositories/snapshots + spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ @@ -97,7 +101,13 @@ jar provided
    - + + net.md-5 + bungeecord-api + 1.12-SNAPSHOT + jar + provided + diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java index 809783300..6659fdb50 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java @@ -16,14 +16,16 @@ import com.djrapitops.pluginbridge.plan.griefprevention.GriefPreventionHook; import com.djrapitops.pluginbridge.plan.griefprevention.plus.GriefPreventionPlusHook; import com.djrapitops.pluginbridge.plan.jobs.JobsHook; import com.djrapitops.pluginbridge.plan.kingdoms.KingdomsHook; -import com.djrapitops.pluginbridge.plan.litebans.LiteBansHook; +import com.djrapitops.pluginbridge.plan.litebans.LiteBansBukkitHook; +import com.djrapitops.pluginbridge.plan.litebans.LiteBansBungeeHook; import com.djrapitops.pluginbridge.plan.mcmmo.McmmoHook; import com.djrapitops.pluginbridge.plan.protocolsupport.ProtocolSupportHook; import com.djrapitops.pluginbridge.plan.redprotect.RedProtectHook; import com.djrapitops.pluginbridge.plan.superbvote.SuperbVoteHook; import com.djrapitops.pluginbridge.plan.towny.TownyHook; import com.djrapitops.pluginbridge.plan.vault.VaultHook; -import com.djrapitops.pluginbridge.plan.viaversion.ViaVersionHook; +import com.djrapitops.pluginbridge.plan.viaversion.ViaVersionBukkitHook; +import com.djrapitops.pluginbridge.plan.viaversion.ViaVersionBungeeHook; /** * Manages connection to other plugins. @@ -56,17 +58,30 @@ public class Bridge { private static Hook[] getHooks(HookHandler h) { Hook[] hooks; - if (Check.isBukkitAvailable()) { - hooks = getBukkitHooks(h); - } else { + if (Check.isBungeeAvailable()) { hooks = getBungeeHooks(h); + } else if (Check.isBukkitAvailable()) { + hooks = getBukkitHooks(h); + } else if (Check.isSpongeAvailable()) { + hooks = getSpongeHooks(h); + } else { + return new Hook[0]; } return hooks; } + private static Hook[] getSpongeHooks(HookHandler h) { + return new Hook[]{ + new BuyCraftHook(h) + }; + } + private static Hook[] getBungeeHooks(HookHandler h) { return new Hook[]{ - new AdvancedBanHook(h) + new AdvancedBanHook(h), + new BuyCraftHook(h), + new LiteBansBungeeHook(h), + new ViaVersionBungeeHook(h) }; } @@ -84,7 +99,7 @@ public class Bridge { new GriefPreventionPlusHook(h), new JobsHook(h), new KingdomsHook(h), - new LiteBansHook(h), + new LiteBansBukkitHook(h), new McmmoHook(h), new SuperbVoteHook(h), new ProtocolSupportHook(h), @@ -92,7 +107,7 @@ public class Bridge { new RedProtectHook(h), new TownyHook(h), new VaultHook(h), - new ViaVersionHook(h)//, + new ViaVersionBukkitHook(h)//, // new PlaceholderAPIHook(h) }; } diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansHook.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansBukkitHook.java similarity index 83% rename from PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansHook.java rename to PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansBukkitHook.java index 67dfc0544..516d3241c 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansHook.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansBukkitHook.java @@ -5,6 +5,7 @@ import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plugin.api.utility.log.Log; import com.djrapitops.pluginbridge.plan.Hook; import litebans.api.Database; +import org.bukkit.Bukkit; /** * A Class responsible for hooking to LiteBans and registering data @@ -13,7 +14,7 @@ import litebans.api.Database; * @author Rsl1122 * @since 3.5.0 */ -public class LiteBansHook extends Hook { +public class LiteBansBukkitHook extends Hook { /** * Hooks the plugin and registers it's PluginData objects. @@ -24,7 +25,7 @@ public class LiteBansHook extends Hook { * @throws NoClassDefFoundError when the plugin class can not be found. */ @SuppressWarnings("ResultOfMethodCallIgnored") - public LiteBansHook(HookHandler hookH) { + public LiteBansBukkitHook(HookHandler hookH) { super(hookH); try { Database.get(); @@ -39,7 +40,8 @@ public class LiteBansHook extends Hook { public void hook() throws NoClassDefFoundError { if (enabled) { - LiteBansDatabaseQueries db = new LiteBansDatabaseQueries(); + String tablePrefix = Bukkit.getPluginManager().getPlugin("LiteBans").getConfig().getString("sql.table_prefix"); + LiteBansDatabaseQueries db = new LiteBansDatabaseQueries(tablePrefix); addPluginDataSource(new LiteBansData(db)); } } diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansBungeeHook.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansBungeeHook.java new file mode 100644 index 000000000..3cfe62196 --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansBungeeHook.java @@ -0,0 +1,69 @@ +package com.djrapitops.pluginbridge.plan.litebans; + +import com.djrapitops.plan.data.plugin.HookHandler; +import com.djrapitops.plan.system.settings.Settings; +import com.djrapitops.plugin.api.utility.log.Log; +import com.djrapitops.pluginbridge.plan.Hook; +import litebans.api.Database; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.ConfigurationProvider; +import net.md_5.bungee.config.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A Class responsible for hooking to LiteBans and registering data + * sources. + * + * @author Rsl1122 + * @since 3.5.0 + */ +public class LiteBansBungeeHook extends Hook { + + /** + * Hooks the plugin and registers it's PluginData objects. + *

    + * API#addPluginDataSource uses the same method from HookHandler. + * + * @param hookH HookHandler instance for registering the data sources. + * @throws NoClassDefFoundError when the plugin class can not be found. + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + public LiteBansBungeeHook(HookHandler hookH) { + super(hookH); + try { + Database.get(); + enabled = true; + } catch (NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError | Exception e) { + if (Settings.DEV_MODE.isTrue()) { + Log.toLog(this.getClass(), e); + } + enabled = false; + } + } + + public void hook() throws NoClassDefFoundError { + if (enabled) { + LiteBansDatabaseQueries db = new LiteBansDatabaseQueries(getTablePrefix()); + addPluginDataSource(new LiteBansData(db)); + } + } + + private String getTablePrefix() { + String tablePrefix = "libeans_"; + try { + File litebansDataFolder = ProxyServer.getInstance().getPluginManager().getPlugin("LiteBans").getDataFolder(); + File configFile = new File(litebansDataFolder, "config.yml"); + + Configuration configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); + tablePrefix = configuration.getString("sql.table_prefix"); + } catch (NullPointerException | IOException e) { + Logger.getLogger("Plan").log(Level.WARNING, "Could not get Litebans table prefix, using default (litebans_). " + e.toString()); + } + return tablePrefix; + } +} diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansDatabaseQueries.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansDatabaseQueries.java index 3c9f90332..f11cfb0b2 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansDatabaseQueries.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansDatabaseQueries.java @@ -5,7 +5,6 @@ import com.djrapitops.plan.system.database.databases.sql.processing.QueryAllStat import com.djrapitops.plan.system.database.databases.sql.processing.QueryStatement; import com.djrapitops.plan.system.database.databases.sql.tables.Table; import litebans.api.Database; -import org.bukkit.Bukkit; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -30,10 +29,9 @@ public class LiteBansDatabaseQueries extends Table { private final String selectSQL; - public LiteBansDatabaseQueries() { + public LiteBansDatabaseQueries(String tablePrefix) { super("litebans", null); database = Database.get(); - String tablePrefix = Bukkit.getPluginManager().getPlugin("LiteBans").getConfig().getString("sql.table_prefix"); banTable = tablePrefix + "bans"; mutesTable = tablePrefix + "mutes"; warningsTable = tablePrefix + "warnings"; diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/PlayerVersionListener.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/BukkitPlayerVersionListener.java similarity index 92% rename from PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/PlayerVersionListener.java rename to PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/BukkitPlayerVersionListener.java index da82d60f8..ca41fbd7e 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/PlayerVersionListener.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/BukkitPlayerVersionListener.java @@ -24,11 +24,11 @@ import java.util.UUID; * @author Rsl1122 * @since 3.5.0 */ -public class PlayerVersionListener implements Listener { +public class BukkitPlayerVersionListener implements Listener { private ViaAPI viaAPI; - public PlayerVersionListener(ViaAPI viaAPI) { + public BukkitPlayerVersionListener(ViaAPI viaAPI) { this.viaAPI = viaAPI; } diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/BungeePlayerVersionListener.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/BungeePlayerVersionListener.java new file mode 100644 index 000000000..382fedc1d --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/BungeePlayerVersionListener.java @@ -0,0 +1,46 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.djrapitops.pluginbridge.plan.viaversion; + +import com.djrapitops.plan.api.exceptions.database.DBOpException; +import com.djrapitops.plan.system.database.databases.Database; +import com.djrapitops.plan.system.database.databases.sql.SQLDB; +import com.djrapitops.plan.system.processing.Processing; +import com.djrapitops.plugin.api.utility.log.Log; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; +import us.myles.ViaVersion.api.ViaAPI; + +import java.util.UUID; + +/** + * Class responsible for listening join events for Version protocol. + * + * @author Rsl1122 + * @since 3.5.0 + */ +public class BungeePlayerVersionListener implements Listener { + + private ViaAPI viaAPI; + + public BungeePlayerVersionListener(ViaAPI viaAPI) { + this.viaAPI = viaAPI; + } + + @EventHandler + public void onJoin(PostLoginEvent event) { + UUID uuid = event.getPlayer().getUniqueId(); + int playerVersion = viaAPI.getPlayerVersion(uuid); + Processing.submitNonCritical(() -> { + try { + new ProtocolTable((SQLDB) Database.getActive()).saveProtocolVersion(uuid, playerVersion); + } catch (DBOpException e) { + Log.toLog(this.getClass(), e); + } + }); + } +} diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/Protocol.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/Protocol.java index b99764fb7..a98417036 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/Protocol.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/Protocol.java @@ -25,6 +25,8 @@ public class Protocol { */ public static String getMCVersion(int protocolVersion) { switch (protocolVersion) { + case 401: + return "1.13.1"; case 390: case 391: case 392: diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/ViaVersionHook.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/ViaVersionBukkitHook.java similarity index 89% rename from PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/ViaVersionHook.java rename to PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/ViaVersionBukkitHook.java index bcaaa9f12..4db2dc27e 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/ViaVersionHook.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/ViaVersionBukkitHook.java @@ -16,7 +16,7 @@ import us.myles.ViaVersion.api.ViaAPI; * @author Rsl1122 * @since 3.1.0 */ -public class ViaVersionHook extends Hook { +public class ViaVersionBukkitHook extends Hook { /** * Hooks the plugin and registers it's PluginData objects. @@ -25,7 +25,7 @@ public class ViaVersionHook extends Hook { * * @param hookH HookHandler instance for registering the data sources. */ - public ViaVersionHook(HookHandler hookH) { + public ViaVersionBukkitHook(HookHandler hookH) { super("us.myles.ViaVersion.ViaVersionPlugin", hookH); } @@ -42,7 +42,7 @@ public class ViaVersionHook extends Hook { Log.toLog(this.getClass().getName(), e); return; } - plan.registerListener(new PlayerVersionListener(api)); + plan.registerListener(new BukkitPlayerVersionListener(api)); addPluginDataSource(new ViaVersionData(table)); } } \ No newline at end of file diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/ViaVersionBungeeHook.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/ViaVersionBungeeHook.java new file mode 100644 index 000000000..41addff02 --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/viaversion/ViaVersionBungeeHook.java @@ -0,0 +1,48 @@ +package com.djrapitops.pluginbridge.plan.viaversion; + +import com.djrapitops.plan.PlanBungee; +import com.djrapitops.plan.api.exceptions.database.DBException; +import com.djrapitops.plan.data.plugin.HookHandler; +import com.djrapitops.plan.system.database.databases.Database; +import com.djrapitops.plan.system.database.databases.sql.SQLDB; +import com.djrapitops.plugin.api.utility.log.Log; +import com.djrapitops.pluginbridge.plan.Hook; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.ViaAPI; + +/** + * A Class responsible for hooking to ViaVersion and registering data sources. + * + * @author Rsl1122 + * @since 3.1.0 + */ +public class ViaVersionBungeeHook extends Hook { + + /** + * Hooks the plugin and registers it's PluginData objects. + *

    + * API#addPluginDataSource uses the same method from HookHandler. + * + * @param hookH HookHandler instance for registering the data sources. + */ + public ViaVersionBungeeHook(HookHandler hookH) { + super("us.myles.ViaVersion.BungeePlugin", hookH); + } + + public void hook() throws NoClassDefFoundError { + if (!enabled) { + return; + } + PlanBungee plan = PlanBungee.getInstance(); + ViaAPI api = Via.getAPI(); + ProtocolTable table = new ProtocolTable((SQLDB) Database.getActive()); + try { + table.createTable(); + } catch (DBException e) { + Log.toLog(this.getClass().getName(), e); + return; + } + plan.registerListener(new BungeePlayerVersionListener(api)); + addPluginDataSource(new ViaVersionData(table)); + } +} \ No newline at end of file From 58512dbd19f82701a68553d9dd747014f58f74ec Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 27 Aug 2018 11:36:37 +0300 Subject: [PATCH 25/76] Fixed uninstalled subcommand --- .../plan/command/commands/manage/ManageUninstalledCommand.java | 3 +-- .../plan/system/database/databases/sql/tables/ServerTable.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageUninstalledCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageUninstalledCommand.java index e42715f52..456f388f0 100644 --- a/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageUninstalledCommand.java +++ b/Plan/src/main/java/com/djrapitops/plan/command/commands/manage/ManageUninstalledCommand.java @@ -3,7 +3,6 @@ package com.djrapitops.plan.command.commands.manage; import com.djrapitops.plan.PlanPlugin; import com.djrapitops.plan.api.exceptions.database.DBOpException; import com.djrapitops.plan.system.database.databases.Database; -import com.djrapitops.plan.system.info.connection.ConnectionSystem; import com.djrapitops.plan.system.info.server.Server; import com.djrapitops.plan.system.info.server.ServerInfo; import com.djrapitops.plan.system.locale.Locale; @@ -69,7 +68,7 @@ public class ManageUninstalledCommand extends CommandNode { } private Optional getServer(String[] args) { - if (args.length >= 1 && ConnectionSystem.getInstance().isServerAvailable()) { + if (args.length >= 1) { Map bukkitServers = Database.getActive().fetch().getBukkitServers(); String serverIdentifier = getGivenIdentifier(args); for (Map.Entry entry : bukkitServers.entrySet()) { diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/ServerTable.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/ServerTable.java index 6df2a8a83..d9a3aaf18 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/ServerTable.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/ServerTable.java @@ -367,7 +367,7 @@ public class ServerTable extends Table { } public void setAsUninstalled(UUID serverUUID) { - String sql = "UPDATE " + tableName + " SET (" + Col.INSTALLED + "=?) WHERE " + Col.SERVER_UUID + "=?"; + String sql = "UPDATE " + tableName + " SET " + Col.INSTALLED + "=? WHERE " + Col.SERVER_UUID + "=?"; execute(new ExecStatement(sql) { @Override From f5b6b43aec905fd2171b83d852e4e4befbc6f0e4 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 27 Aug 2018 11:37:22 +0300 Subject: [PATCH 26/76] Added a progress bar utility --- .../utilities/html/graphs/ProgressBar.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Plan/src/main/java/com/djrapitops/plan/utilities/html/graphs/ProgressBar.java diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/html/graphs/ProgressBar.java b/Plan/src/main/java/com/djrapitops/plan/utilities/html/graphs/ProgressBar.java new file mode 100644 index 000000000..a29ebc932 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/html/graphs/ProgressBar.java @@ -0,0 +1,39 @@ +package com.djrapitops.plan.utilities.html.graphs; + +import com.djrapitops.plan.data.store.mutators.formatting.Formatters; + +/** + * Utility for creating ProgressBars. + * + * @author Rsl1122 + */ +public class ProgressBar { + + private final int obtained; + private final int max; + + private final String color; + + public ProgressBar(int obtained, int max) { + this(obtained, max, "teal"); + } + + public ProgressBar(int obtained, int max, String color) { + this.obtained = obtained; + this.max = max; + this.color = color; + } + + public String toHtml() { + double percentage = obtained * 1.0 / max; + int percentageRounded = (int) percentage; + + return "

    " + + obtained + " / " + max + " (" + Formatters.percentage().apply(percentage) + ")" + + "
    "; + } + +} \ No newline at end of file From 870b929a9e7befcc8aa3f465c0786677dd85868d Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 27 Aug 2018 11:37:57 +0300 Subject: [PATCH 27/76] Added ASkyBlock challenges to player page #686 --- .../plan/askyblock/ASkyBlockData.java | 36 ++++++++++++++++++- .../plan/litebans/LiteBansData.java | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/askyblock/ASkyBlockData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/askyblock/ASkyBlockData.java index 08516f12d..fc3640f07 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/askyblock/ASkyBlockData.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/askyblock/ASkyBlockData.java @@ -6,14 +6,18 @@ package com.djrapitops.pluginbridge.plan.askyblock; import com.djrapitops.plan.data.element.AnalysisContainer; import com.djrapitops.plan.data.element.InspectContainer; +import com.djrapitops.plan.data.element.TableContainer; import com.djrapitops.plan.data.plugin.ContainerSize; import com.djrapitops.plan.data.plugin.PluginData; +import com.djrapitops.plan.utilities.html.graphs.ProgressBar; import com.djrapitops.plan.utilities.html.icon.Color; import com.djrapitops.plan.utilities.html.icon.Family; import com.djrapitops.plan.utilities.html.icon.Icon; +import com.djrapitops.plugin.utilities.Format; import com.wasteofplastic.askyblock.ASkyBlockAPI; import java.util.Collection; +import java.util.Map; import java.util.UUID; /** @@ -35,7 +39,7 @@ public class ASkyBlockData extends PluginData { public InspectContainer getPlayerData(UUID uuid, InspectContainer inspectContainer) { if (api.hasIsland(uuid)) { String islandName = api.getIslandName(uuid); - int level = api.getIslandLevel(uuid); + long level = api.getLongIslandLevel(uuid); int resetsLeft = api.getResetsLeft(uuid); inspectContainer.addValue(getWithIcon("Island Name", Icon.called("street-view").of(Color.GREEN)), islandName); @@ -45,9 +49,39 @@ public class ASkyBlockData extends PluginData { inspectContainer.addValue(getWithIcon("Island Name", Icon.called("street-view").of(Color.GREEN)), "No Island"); } + Map challengeCompletion = api.getChallengeTimes(uuid); + int obtained = (int) challengeCompletion.values().stream().filter(value -> value != 0).count(); + int max = challengeCompletion.size(); + + inspectContainer.addValue(getWithIcon("Challenge Progress", Icon.called("bookmark").of(Color.LIGHT_BLUE)), obtained + " / " + max); + ProgressBar challengeProgress = new ProgressBar(obtained, max, "light-blue"); + inspectContainer.addHtml("challenge-progress", challengeProgress.toHtml()); + + addTable(inspectContainer, challengeCompletion); + return inspectContainer; } + private void addTable(InspectContainer inspectContainer, Map challengeCompletion) { + TableContainer challenges = new TableContainer( + getWithIcon("Challenge", Icon.called("bookmark")), + getWithIcon("Times completed", Icon.called("check")) + ); + challenges.setColor("light-blue"); + challengeCompletion.entrySet().stream() + .sorted((one, two) -> Integer.compare(two.getValue(), one.getValue())) + .forEach(entry -> { + String challenge = new Format(entry.getKey()).capitalize().toString(); + Integer completionTimes = entry.getValue(); + boolean complete = completionTimes > 0; + challenges.addRow( + "" + challenge + "", + completionTimes + ); + }); + inspectContainer.addTable("challenge-table", challenges); + } + @Override public AnalysisContainer getServerData(Collection uuids, AnalysisContainer analysisContainer) { int islandCount = api.getIslandCount(); diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansData.java index 48cde675c..f58704be9 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansData.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansData.java @@ -44,7 +44,7 @@ public class LiteBansData extends PluginData implements BanData { public InspectContainer getPlayerData(UUID uuid, InspectContainer inspectContainer) { inspectContainer.addValue(Icon.called("balance-scale").of(Color.RED) + - "Hover over 'What' column entry for offence reasons", ""); + " Hover over 'What' column entry for offence reasons", ""); String what = getWithIcon("Effect", Icon.called("times-circle").of(Family.REGULAR)); String by = getWithIcon("By", Icon.called("gavel")); From f610e014c10dd39fb8405c92cfa7bdfeed50fa65 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 27 Aug 2018 11:45:51 +0300 Subject: [PATCH 28/76] Cleaned up AdvancedBanData class --- .../plan/advancedban/AdvancedBanData.java | 94 +++++++++---------- 1 file changed, 42 insertions(+), 52 deletions(-) diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/advancedban/AdvancedBanData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/advancedban/AdvancedBanData.java index 31f4bfae2..377083f1a 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/advancedban/AdvancedBanData.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/advancedban/AdvancedBanData.java @@ -9,7 +9,6 @@ import com.djrapitops.plan.data.element.AnalysisContainer; import com.djrapitops.plan.data.element.InspectContainer; import com.djrapitops.plan.data.plugin.ContainerSize; import com.djrapitops.plan.data.plugin.PluginData; -import com.djrapitops.plan.data.store.keys.AnalysisKeys; import com.djrapitops.plan.utilities.FormatUtils; import com.djrapitops.plan.utilities.html.Html; import com.djrapitops.plan.utilities.html.HtmlUtils; @@ -17,15 +16,13 @@ import com.djrapitops.plan.utilities.html.icon.Color; import com.djrapitops.plan.utilities.html.icon.Family; import com.djrapitops.plan.utilities.html.icon.Icon; import com.djrapitops.plan.utilities.html.icon.Icons; -import com.djrapitops.plugin.api.utility.log.Log; -import java.util.Collection; -import java.util.HashMap; -import java.util.UUID; import me.leoko.advancedban.manager.PunishmentManager; -import me.leoko.advancedban.manager.UUIDManager; import me.leoko.advancedban.utils.Punishment; import me.leoko.advancedban.utils.PunishmentType; +import java.util.Collection; +import java.util.UUID; + /** * PluginData for AdvancedBan plugin. * @@ -45,59 +42,52 @@ public class AdvancedBanData extends PluginData { return inspectContainer; } - Punishment ban = PunishmentManager.get().getBan(abUuid); - Punishment mute = PunishmentManager.get().getMute(abUuid); - long warnings = PunishmentManager.get().getWarns(abUuid).stream().filter(warning -> !warning.isExpired()).count(); + PunishmentManager punishmentManager = PunishmentManager.get(); + Punishment ban = punishmentManager.getBan(abUuid); + Punishment mute = punishmentManager.getMute(abUuid); + long warnings = punishmentManager.getWarns(abUuid).stream().filter(warning -> !warning.isExpired()).count(); inspectContainer.addValue(getWithIcon("Banned", Icons.BANNED), ban != null ? "Yes" : "No"); - inspectContainer.addValue(getWithIcon("Muted", Icon.called("bell-slash").of(Color.DEEP_ORANGE)), mute != null ? "Yes" : "No"); + if (ban != null) { + addPunishment(inspectContainer, ban, "Permanent ban"); + } + + inspectContainer.addValue(getWithIcon("Muted", Icon.called("bell-slash").of(Color.DEEP_ORANGE)), mute != null ? "Yes" : "No"); + if (mute != null) { + addPunishment(inspectContainer, mute, "Permanent mute"); + } + inspectContainer.addValue(getWithIcon("Warnings", Icon.called("flag").of(Color.YELLOW)), warnings); - if (ban != null) { - String operator = ban.getOperator(); - String link = Html.LINK.parse(PlanAPI.getInstance().getPlayerInspectPageLink(operator), operator); - String reason = HtmlUtils.swapColorsToSpan(ban.getReason()); - long start = ban.getStart(); - String end = FormatUtils.formatTimeStampYear(ban.getEnd()); - - if (ban.getType() == PunishmentType.BAN || ban.getType() == PunishmentType.IP_BAN) { // Permanent - end = "Permanent ban"; - } - - if (operator.equals("CONSOLE")) { - link = "CONSOLE"; - } - - inspectContainer.addValue(" " + getWithIcon("Operator", Icon.called("user").of(Color.RED)), link); - inspectContainer.addValue(" " + getWithIcon("Date", Icon.called("calendar").of(Color.RED).of(Family.REGULAR)), FormatUtils.formatTimeStampYear(start)); - inspectContainer.addValue(" " + getWithIcon("Ends", Icon.called("calendar-check").of(Color.RED).of(Family.REGULAR)), end); - inspectContainer.addValue(" " + getWithIcon("Reason", Icon.called("comment").of(Color.RED).of(Family.REGULAR)), reason); - } - - if (mute != null) { - String operator = mute.getOperator(); - String link = Html.LINK.parse(PlanAPI.getInstance().getPlayerInspectPageLink(operator), operator); - String reason = HtmlUtils.swapColorsToSpan(mute.getReason()); - long start = mute.getStart(); - String end = FormatUtils.formatTimeStampYear(mute.getEnd()); - - if (mute.getType() == PunishmentType.MUTE) { // Permanent - end = "Permanent mute"; - } - - if (operator.equals("CONSOLE")) { - link = "CONSOLE"; - } - - inspectContainer.addValue(" " + getWithIcon("Operator", Icon.called("user").of(Color.DEEP_ORANGE)), link); - inspectContainer.addValue(" " + getWithIcon("Date", Icon.called("calendar").of(Color.DEEP_ORANGE).of(Family.REGULAR)), FormatUtils.formatTimeStampYear(start)); - inspectContainer.addValue(" " + getWithIcon("Ends", Icon.called("calendar-check").of(Color.DEEP_ORANGE).of(Family.REGULAR)), end); - inspectContainer.addValue(" " + getWithIcon("Reason", Icon.called("comment").of(Color.DEEP_ORANGE).of(Family.REGULAR)), reason); - } - return inspectContainer; } + private void addPunishment(InspectContainer inspectContainer, Punishment punishment, String identifier) { + String operator = punishment.getOperator(); + String link = Html.LINK.parse(PlanAPI.getInstance().getPlayerInspectPageLink(operator), operator); + String reason = HtmlUtils.swapColorsToSpan(punishment.getReason()); + String start = FormatUtils.formatTimeStampYear(punishment.getStart()); + String end = FormatUtils.formatTimeStampYear(punishment.getEnd()); + + PunishmentType type = punishment.getType(); + // Permanent + if (type == PunishmentType.BAN + || type == PunishmentType.IP_BAN + || type == PunishmentType.MUTE + ) { + end = identifier; + } + + if (operator.equals("CONSOLE")) { + link = "CONSOLE"; + } + + inspectContainer.addValue(" " + getWithIcon("Operator", Icon.called("user").of(Color.RED)), link); + inspectContainer.addValue(" " + getWithIcon("Date", Icon.called("calendar").of(Color.RED).of(Family.REGULAR)), start); + inspectContainer.addValue(" " + getWithIcon("Ends", Icon.called("calendar-check").of(Color.RED).of(Family.REGULAR)), end); + inspectContainer.addValue(" " + getWithIcon("Reason", Icon.called("comment").of(Color.RED).of(Family.REGULAR)), reason); + } + @Override public AnalysisContainer getServerData(Collection uuids, AnalysisContainer analysisContainer) { return analysisContainer; From e09f821071137e25aeb934bca248026f2a5b7ed2 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 27 Aug 2018 11:49:41 +0300 Subject: [PATCH 29/76] Updated PluginBridge jar --- PlanPluginBridge/PlanPluginBridge-4.4.0.jar | Bin 145435 -> 156141 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/PlanPluginBridge/PlanPluginBridge-4.4.0.jar b/PlanPluginBridge/PlanPluginBridge-4.4.0.jar index fdf51327bee2e47098a5e8b434de4d999e93c4cf..c39acfceac167d6b506b941bceb939e4417443ae 100644 GIT binary patch delta 41620 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 From 86f99cbe1beed83e2b17c537c0bbd222cd0daa5a Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 27 Aug 2018 12:02:00 +0300 Subject: [PATCH 30/76] Fixed Sponge CPU usage gathering --- .../plan/system/tasks/TPSCountTimer.java | 25 +++++++++++++++++++ .../tasks/bungee/BungeeTPSCountTimer.java | 4 +++ .../tasks/server/BukkitTPSCountTimer.java | 24 +----------------- .../tasks/server/SpongeTPSCountTimer.java | 16 ++---------- 4 files changed, 32 insertions(+), 37 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/TPSCountTimer.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/TPSCountTimer.java index ba94523c3..93fe4cd32 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/TPSCountTimer.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/TPSCountTimer.java @@ -7,6 +7,8 @@ import com.djrapitops.plan.system.processing.processors.TPSInsertProcessor; import com.djrapitops.plugin.api.utility.log.Log; import com.djrapitops.plugin.task.AbsRunnable; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; import java.util.ArrayList; import java.util.List; @@ -52,4 +54,27 @@ public abstract class TPSCountTimer extends AbsRunnable { public int getLatestPlayersOnline() { return latestPlayersOnline; } + + protected long getUsedMemory() { + Runtime runtime = Runtime.getRuntime(); + long totalMemory = runtime.totalMemory(); + return (totalMemory - runtime.freeMemory()) / 1000000; + } + + protected double getCPUUsage() { + double averageCPUUsage; + + OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); + if (osBean instanceof com.sun.management.OperatingSystemMXBean) { + com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean; + averageCPUUsage = nativeOsBean.getSystemCpuLoad(); + } else { + int availableProcessors = osBean.getAvailableProcessors(); + averageCPUUsage = osBean.getSystemLoadAverage() / availableProcessors; + } + if (averageCPUUsage < 0) { // If unavailable, getSystemLoadAverage() returns -1 + averageCPUUsage = -1; + } + return averageCPUUsage * 100.0; + } } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/bungee/BungeeTPSCountTimer.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/bungee/BungeeTPSCountTimer.java index 2d5edb328..dda2d8233 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/bungee/BungeeTPSCountTimer.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/bungee/BungeeTPSCountTimer.java @@ -19,6 +19,10 @@ public class BungeeTPSCountTimer extends TPSCountTimer { .date(now) .skipTPS() .playersOnline(onlineCount) + .usedCPU(getCPUUsage()) + .usedMemory(getUsedMemory()) + .entities(-1) + .chunksLoaded(-1) .toTPS(); history.add(tps); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/BukkitTPSCountTimer.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/BukkitTPSCountTimer.java index 7ace91d0a..1017dea99 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/BukkitTPSCountTimer.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/BukkitTPSCountTimer.java @@ -7,9 +7,6 @@ import com.djrapitops.plugin.api.TimeAmount; import com.djrapitops.plugin.api.utility.log.Log; import org.bukkit.World; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; - public class BukkitTPSCountTimer extends TPSCountTimer { private long lastCheckNano; @@ -43,10 +40,7 @@ public class BukkitTPSCountTimer extends TPSCountTimer { private TPS calculateTPS(long diff, long now) { double averageCPUUsage = getCPUUsage(); - Runtime runtime = Runtime.getRuntime(); - - long totalMemory = runtime.totalMemory(); - long usedMemory = (totalMemory - runtime.freeMemory()) / 1000000; + long usedMemory = getUsedMemory(); int playersOnline = plugin.getServer().getOnlinePlayers().size(); latestPlayersOnline = playersOnline; @@ -58,22 +52,6 @@ public class BukkitTPSCountTimer extends TPSCountTimer { return getTPS(diff, now, averageCPUUsage, usedMemory, entityCount, loadedChunks, playersOnline); } - private double getCPUUsage() { - double averageCPUUsage; - - OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); - if (osBean instanceof com.sun.management.OperatingSystemMXBean) { - com.sun.management.OperatingSystemMXBean nativeOsBean = (com.sun.management.OperatingSystemMXBean) osBean; - averageCPUUsage = nativeOsBean.getSystemCpuLoad(); - } else { - int availableProcessors = osBean.getAvailableProcessors(); - averageCPUUsage = osBean.getSystemLoadAverage() / availableProcessors; - } - if (averageCPUUsage < 0) { // If unavailable, getSystemLoadAverage() returns -1 - averageCPUUsage = -1; - } - return averageCPUUsage * 100.0; - } /** * Gets the TPS for Spigot / Bukkit diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/SpongeTPSCountTimer.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/SpongeTPSCountTimer.java index 2e3356bab..a3ce43333 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/SpongeTPSCountTimer.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/SpongeTPSCountTimer.java @@ -9,9 +9,6 @@ import com.djrapitops.plugin.api.utility.log.Log; import org.spongepowered.api.Sponge; import org.spongepowered.api.world.World; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; - public class SpongeTPSCountTimer extends TPSCountTimer { private long lastCheckNano; @@ -42,18 +39,9 @@ public class SpongeTPSCountTimer extends TPSCountTimer { * @return the TPS */ private TPS calculateTPS(long now) { - OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); - int availableProcessors = operatingSystemMXBean.getAvailableProcessors(); - double averageCPUUsage = operatingSystemMXBean.getSystemLoadAverage() / availableProcessors * 100.0; + double averageCPUUsage = getCPUUsage(); - if (averageCPUUsage < 0) { // If unavailable, getSystemLoadAverage() returns -1 - averageCPUUsage = -1; - } - - Runtime runtime = Runtime.getRuntime(); - - long totalMemory = runtime.totalMemory(); - long usedMemory = (totalMemory - runtime.freeMemory()) / 1000000; + long usedMemory = getUsedMemory(); double tps = Sponge.getGame().getServer().getTicksPerSecond(); int playersOnline = ServerInfo.getServerProperties().getOnlinePlayers(); From a21b91fae8d8230009185ee838434f1c55be18a2 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 27 Aug 2018 12:02:12 +0300 Subject: [PATCH 31/76] Version bump to 4.4.5 --- Plan/src/main/java/com/djrapitops/plan/PlanSponge.java | 2 +- Plan/src/main/resources/bungee.yml | 2 +- Plan/src/main/resources/plugin.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java b/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java index 60ece7eba..543b60759 100644 --- a/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java +++ b/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java @@ -25,7 +25,7 @@ import org.spongepowered.api.plugin.Plugin; import java.io.File; import java.io.InputStream; -@Plugin(id = "plan", name = "Plan", version = "4.4.4", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"}) +@Plugin(id = "plan", name = "Plan", version = "4.4.5", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"}) public class PlanSponge extends SpongePlugin implements PlanPlugin { @Inject diff --git a/Plan/src/main/resources/bungee.yml b/Plan/src/main/resources/bungee.yml index 365f2b64d..0b1e9b6f6 100644 --- a/Plan/src/main/resources/bungee.yml +++ b/Plan/src/main/resources/bungee.yml @@ -1,4 +1,4 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.PlanBungee -version: 4.4.4 \ No newline at end of file +version: 4.4.5 \ No newline at end of file diff --git a/Plan/src/main/resources/plugin.yml b/Plan/src/main/resources/plugin.yml index 8583e4134..55c0b3fe7 100644 --- a/Plan/src/main/resources/plugin.yml +++ b/Plan/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.Plan -version: 4.4.4 +version: 4.4.5 softdepend: - EssentialsX - Towny From 91905bc665273dac659edf5e10c41615e9620690 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 27 Aug 2018 13:02:41 +0300 Subject: [PATCH 32/76] Update versions.txt --- versions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/versions.txt b/versions.txt index 08f3ef568..8f575e18b 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,4 @@ +REL|4.4.5|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.5-RELEASE/Plan-4.4.5.Bukkit.Bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.5-RELEASE REL|4.4.4|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.4-REL/Plan-4.4.4.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=240584 DEV|4.4.3-b3|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.4-DEV3/Plan-4.4.3-b3.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.4-DEV3 REL|4.4.3|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.3/Plan-4.4.3.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=238545 From 9898bf8264d4716bf2cd7a7936020ef744ddbc8e Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 27 Aug 2018 15:23:46 +0300 Subject: [PATCH 33/76] Bukkit/Bungee pom - comment relocation & uncomment ignore for Sponge pom --- Plan/dependency-reduced-pom.xml | 3 --- Plan/pom.xml | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Plan/dependency-reduced-pom.xml b/Plan/dependency-reduced-pom.xml index a8ec7baba..7b3d46293 100644 --- a/Plan/dependency-reduced-pom.xml +++ b/Plan/dependency-reduced-pom.xml @@ -83,9 +83,6 @@ org.slf4j plan.org.slf4j - - org.slf4j.Logger - org.bstats diff --git a/Plan/pom.xml b/Plan/pom.xml index baf436b60..9f16f2e25 100644 --- a/Plan/pom.xml +++ b/Plan/pom.xml @@ -223,6 +223,7 @@ org.mockito:* org.easymock:* junit:* + @@ -248,9 +249,6 @@ org.slf4j plan.org.slf4j - - org.slf4j.Logger - org.bstats From 192c06354bf3f860afbb6e726091689f7af2909a Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Tue, 28 Aug 2018 15:33:40 +0300 Subject: [PATCH 34/76] Add install_dependencies.bat --- PlanPluginBridge/install_dependencies.bat | 1 + 1 file changed, 1 insertion(+) create mode 100644 PlanPluginBridge/install_dependencies.bat diff --git a/PlanPluginBridge/install_dependencies.bat b/PlanPluginBridge/install_dependencies.bat new file mode 100644 index 000000000..4558c418a --- /dev/null +++ b/PlanPluginBridge/install_dependencies.bat @@ -0,0 +1 @@ +mvn install:install-file -Dfile="AAC-3.5.0-b1.jar" -DgroupId=me.konsolas -DartifactId=AAC -Dversion=3.5.0 -Dpackaging=jar & mvn install:install-file -Dfile="AdvancedBan-2.1.5.jar" -DgroupId=me.leoko -DartifactId=advancedban -Dversion=2.1.5 -Dpackaging=jar & mvn install:install-file -Dfile="ASkyBlock-3.0.9.4.jar" -DgroupId=com.wasteofplastic.askyblock -DartifactId=ASkyBlock -Dversion=3.0.9.4 -Dpackaging=jar & mvn install:install-file -Dfile="BanManager-5.15.0.jar" -DgroupId=me.confuser -DartifactId=banmanager -Dversion=5.15.0 -Dpackaging=jar & mvn install:install-file -Dfile="EssentialsX-2.15.0.1.jar" -DgroupId=net.ess3 -DartifactId=EssentialsX -Dversion=2.15.0.1 -Dpackaging=jar & mvn install:install-file -Dfile="Factions-2.14.0.jar" -DgroupId=com.massivecraft -DartifactId=factions -Dversion=2.14.0 -Dpackaging=jar & mvn install:install-file -Dfile="MassiveCore-2.14.0.jar" -DgroupId=com.massivecraft -DartifactId=mcore -Dversion=2.14.0 -Dpackaging=jar & mvn install:install-file -Dfile="GriefPreventionPlus-13.3.jar" -DgroupId=net.kaikk.mc -DartifactId=GriefPreventionPlus -Dversion=13.3 -Dpackaging=jar & mvn install:install-file -Dfile="Jobs4.7.4.jar" -DgroupId=com.gamingmesh -DartifactId=jobs -Dversion=4.7.4 -Dpackaging=jar & mvn install:install-file -Dfile="Kingdoms-13.3.40.jar" -DgroupId=org.kingdoms -DartifactId=kingdoms_demo -Dversion=13.3.40 -Dpackaging=jar & mvn install:install-file -Dfile="LiteBansAPI-0.3.jar" -DgroupId=litebans -DartifactId=api -Dversion=0.3 -Dpackaging=jar & mvn install:install-file -Dfile="mcMMO-1.6.0-SNAPSHOT.jar" -DgroupId=com.gmail.nossr50 -DartifactId=mcMMO -Dversion=1.6.0 -Dpackaging=jar & mvn install:install-file -Dfile="ProtocolSupport-4.28.jar" -DgroupId=com.github.ProtocolSupport -DartifactId=ProtocolSupport -Dversion=4.28 -Dpackaging=jar & mvn install:install-file -Dfile="React-6.573.jar" -DgroupId=com.volmit -DartifactId=react -Dversion=6.573 -Dpackaging=jar & mvn install:install-file -Dfile="SuperbVote-0.5.3.jar" -DgroupId=io.minimum -DartifactId=minecraft.superbvote -Dversion=0.5.3 -Dpackaging=jar & mvn install:install-file -Dfile="Towny-0.92.0.0.jar" -DgroupId=com.palmergames -DartifactId=towny -Dversion=0.92.0.0 -Dpackaging=jar & mvn install:install-file -Dfile="Vault-1.6.7.jar" -DgroupId=net.milkbowl.vault -DartifactId=VaultAPI -Dversion=1.6 -Dpackaging=jar \ No newline at end of file From fd5e02e1c9f3bfb002d024d8fb0e42fa01a003c2 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 30 Aug 2018 16:11:34 +0300 Subject: [PATCH 35/76] Reduced html page size by compressing result html #685 --- Plan/pom.xml | 7 +++++ .../response/errors/ErrorResponse.java | 4 +-- .../response/pages/AnalysisPageResponse.java | 3 +-- .../response/pages/InspectPageResponse.java | 3 +-- .../response/pages/NetworkPageResponse.java | 3 +-- .../response/pages/PageResponse.java | 27 +++++++++++++++++++ .../response/pages/PlayersPageResponse.java | 3 +-- .../parts/InspectPagePluginsContent.java | 4 +-- .../pages/parts/NetworkPageContent.java | 4 +-- 9 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/PageResponse.java diff --git a/Plan/pom.xml b/Plan/pom.xml index 9f16f2e25..ea29ad87b 100644 --- a/Plan/pom.xml +++ b/Plan/pom.xml @@ -139,6 +139,13 @@ 1.2
    + + + com.googlecode.htmlcompressor + htmlcompressor + 1.5.2 + + org.mockito diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/errors/ErrorResponse.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/errors/ErrorResponse.java index d8d2ecd9e..78aa8810a 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/errors/ErrorResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/errors/ErrorResponse.java @@ -5,7 +5,7 @@ package com.djrapitops.plan.system.webserver.response.errors; import com.djrapitops.plan.system.settings.theme.Theme; -import com.djrapitops.plan.system.webserver.response.Response; +import com.djrapitops.plan.system.webserver.response.pages.PageResponse; import com.djrapitops.plan.utilities.MiscUtils; import com.djrapitops.plan.utilities.file.FileUtil; import com.djrapitops.plugin.api.utility.log.Log; @@ -20,7 +20,7 @@ import java.util.Map; * * @author Rsl1122 */ -public class ErrorResponse extends Response { +public class ErrorResponse extends PageResponse { private String title; private String paragraph; diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/AnalysisPageResponse.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/AnalysisPageResponse.java index 644f7697f..9d3655458 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/AnalysisPageResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/AnalysisPageResponse.java @@ -7,7 +7,6 @@ import com.djrapitops.plan.system.info.InfoSystem; import com.djrapitops.plan.system.processing.Processing; import com.djrapitops.plan.system.webserver.cache.PageId; import com.djrapitops.plan.system.webserver.cache.ResponseCache; -import com.djrapitops.plan.system.webserver.response.Response; import com.djrapitops.plan.system.webserver.response.errors.NotFoundResponse; import com.djrapitops.plan.utilities.html.pages.AnalysisPage; import com.djrapitops.plugin.api.utility.log.Log; @@ -18,7 +17,7 @@ import java.util.UUID; * @author Rsl1122 * @since 3.5.2 */ -public class AnalysisPageResponse extends Response { +public class AnalysisPageResponse extends PageResponse { public static AnalysisPageResponse refreshNow(UUID serverUUID) { Processing.submitNonCritical(() -> { diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/InspectPageResponse.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/InspectPageResponse.java index bf41399f1..926f474ee 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/InspectPageResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/InspectPageResponse.java @@ -3,7 +3,6 @@ package com.djrapitops.plan.system.webserver.response.pages; import com.djrapitops.plan.system.settings.theme.Theme; import com.djrapitops.plan.system.webserver.cache.PageId; import com.djrapitops.plan.system.webserver.cache.ResponseCache; -import com.djrapitops.plan.system.webserver.response.Response; import com.djrapitops.plan.system.webserver.response.errors.ErrorResponse; import com.djrapitops.plan.system.webserver.response.pages.parts.InspectPagePluginsContent; import org.apache.commons.text.StringSubstitutor; @@ -16,7 +15,7 @@ import java.util.UUID; * @author Rsl1122 * @since 3.5.2 */ -public class InspectPageResponse extends Response { +public class InspectPageResponse extends PageResponse { private final UUID uuid; diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/NetworkPageResponse.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/NetworkPageResponse.java index 5dda443a6..e2316491c 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/NetworkPageResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/NetworkPageResponse.java @@ -3,7 +3,6 @@ package com.djrapitops.plan.system.webserver.response.pages; import com.djrapitops.plan.api.exceptions.ParseException; import com.djrapitops.plan.data.store.containers.NetworkContainer; import com.djrapitops.plan.system.database.databases.Database; -import com.djrapitops.plan.system.webserver.response.Response; import com.djrapitops.plan.utilities.html.pages.NetworkPage; /** @@ -11,7 +10,7 @@ import com.djrapitops.plan.utilities.html.pages.NetworkPage; * * @author Rsl1122 */ -public class NetworkPageResponse extends Response { +public class NetworkPageResponse extends PageResponse { public NetworkPageResponse() throws ParseException { super.setHeader("HTTP/1.1 200 OK"); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/PageResponse.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/PageResponse.java new file mode 100644 index 000000000..6b5ceac0e --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/PageResponse.java @@ -0,0 +1,27 @@ +package com.djrapitops.plan.system.webserver.response.pages; + +import com.djrapitops.plan.system.webserver.response.Response; +import com.djrapitops.plan.system.webserver.response.ResponseType; +import com.googlecode.htmlcompressor.compressor.HtmlCompressor; + +/** + * Response for all HTML Page responses. + * + * @author Rsl1122 + */ +public class PageResponse extends Response { + + public PageResponse(ResponseType type) { + super(type); + } + + public PageResponse() { + } + + @Override + public void setContent(String content) { + HtmlCompressor compressor = new HtmlCompressor(); + compressor.setRemoveIntertagSpaces(true); + super.setContent(compressor.compress(content)); + } +} \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/PlayersPageResponse.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/PlayersPageResponse.java index 2b805db9f..d076bb0dd 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/PlayersPageResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/PlayersPageResponse.java @@ -1,7 +1,6 @@ package com.djrapitops.plan.system.webserver.response.pages; import com.djrapitops.plan.api.exceptions.ParseException; -import com.djrapitops.plan.system.webserver.response.Response; import com.djrapitops.plan.system.webserver.response.errors.InternalErrorResponse; import com.djrapitops.plan.utilities.html.pages.PlayersPage; import com.djrapitops.plugin.api.utility.log.Log; @@ -10,7 +9,7 @@ import com.djrapitops.plugin.api.utility.log.Log; * @author Rsl1122 * @since 3.5.2 */ -public class PlayersPageResponse extends Response { +public class PlayersPageResponse extends PageResponse { public PlayersPageResponse() { super.setHeader("HTTP/1.1 200 OK"); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/parts/InspectPagePluginsContent.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/parts/InspectPagePluginsContent.java index d58abe252..bcba4c45c 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/parts/InspectPagePluginsContent.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/parts/InspectPagePluginsContent.java @@ -8,7 +8,7 @@ import com.djrapitops.plan.data.element.InspectContainer; import com.djrapitops.plan.data.plugin.HookHandler; import com.djrapitops.plan.data.plugin.PluginData; import com.djrapitops.plan.system.info.server.ServerInfo; -import com.djrapitops.plan.system.webserver.response.Response; +import com.djrapitops.plan.system.webserver.response.pages.PageResponse; import com.djrapitops.plan.utilities.comparators.PluginDataNameComparator; import com.djrapitops.plan.utilities.html.Html; import com.djrapitops.plan.utilities.html.HtmlStructure; @@ -23,7 +23,7 @@ import java.util.*; * * @author Rsl1122 */ -public class InspectPagePluginsContent extends Response { +public class InspectPagePluginsContent extends PageResponse { // ServerUUID, {nav, html} private final Map pluginsTab; diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/parts/NetworkPageContent.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/parts/NetworkPageContent.java index 3eaa8bfb3..16999e6c3 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/parts/NetworkPageContent.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/parts/NetworkPageContent.java @@ -4,7 +4,7 @@ */ package com.djrapitops.plan.system.webserver.response.pages.parts; -import com.djrapitops.plan.system.webserver.response.Response; +import com.djrapitops.plan.system.webserver.response.pages.PageResponse; import java.util.*; @@ -15,7 +15,7 @@ import java.util.*; * * @author Rsl1122 */ -public class NetworkPageContent extends Response { +public class NetworkPageContent extends PageResponse { private final Map content; From c30650c0ee5ada9f9aa5d8e5114c05cad66f8861 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 30 Aug 2018 16:36:14 +0300 Subject: [PATCH 36/76] Changed ResponseCache to use caffeine (5 minute invalidation) #685 --- Plan/dependency-reduced-pom.xml | 4 --- Plan/pom.xml | 7 ++++ .../system/webserver/cache/ResponseCache.java | 35 +++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Plan/dependency-reduced-pom.xml b/Plan/dependency-reduced-pom.xml index 7b3d46293..4a214d000 100644 --- a/Plan/dependency-reduced-pom.xml +++ b/Plan/dependency-reduced-pom.xml @@ -306,10 +306,6 @@ guice com.google.inject - - caffeine - com.github.ben-manes.caffeine - guava com.github.ben-manes.caffeine diff --git a/Plan/pom.xml b/Plan/pom.xml index ea29ad87b..316381126 100644 --- a/Plan/pom.xml +++ b/Plan/pom.xml @@ -122,6 +122,13 @@ 2.9.0 + + + com.github.ben-manes.caffeine + caffeine + 2.6.2 + + org.bstats diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/cache/ResponseCache.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/cache/ResponseCache.java index 772e04180..0834953af 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/cache/ResponseCache.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/cache/ResponseCache.java @@ -1,10 +1,11 @@ package com.djrapitops.plan.system.webserver.cache; import com.djrapitops.plan.system.webserver.response.Response; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; -import java.util.HashMap; -import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; /** @@ -19,7 +20,9 @@ import java.util.function.Supplier; */ public class ResponseCache { - private static final Map cache = new HashMap<>(); + private static final Cache cache = Caffeine.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .build(); /** * Constructor used to hide the public constructor @@ -41,17 +44,7 @@ public class ResponseCache { * @return The Response that was cached or created by the the {@link Response} {@link Supplier} */ public static Response loadResponse(String identifier, Supplier loader) { - Response response = loadResponse(identifier); - - if (response != null) { - return response; - } - - response = loader.get(); - - cache.put(identifier, response); - - return response; + return cache.get(identifier, k -> loader.get()); } /** @@ -61,7 +54,7 @@ public class ResponseCache { * @return The Response that was cached or {@code null} if it wasn't */ public static Response loadResponse(String identifier) { - return cache.get(identifier); + return cache.getIfPresent(identifier); } /** @@ -84,21 +77,25 @@ public class ResponseCache { * @return true if the page is cached */ public static boolean isCached(String identifier) { - return cache.containsKey(identifier); + return cache.getIfPresent(identifier) != null; } /** * Clears the cache from all its contents. */ public static void clearCache() { - cache.clear(); + cache.invalidateAll(); } public static Set getCacheKeys() { - return cache.keySet(); + return cache.asMap().keySet(); + } + + public static long getEstimatedSize() { + return cache.estimatedSize(); } public static void clearResponse(String identifier) { - cache.remove(identifier); + cache.invalidate(identifier); } } From 446aec2a0a5ee7b8f5ed204c7f2ff61adabc96c1 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 30 Aug 2018 17:18:57 +0300 Subject: [PATCH 37/76] Reproduced issue #693 in a test --- .../listeners/bukkit/AFKListenerTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Plan/src/test/java/com/djrapitops/plan/system/listeners/bukkit/AFKListenerTest.java diff --git a/Plan/src/test/java/com/djrapitops/plan/system/listeners/bukkit/AFKListenerTest.java b/Plan/src/test/java/com/djrapitops/plan/system/listeners/bukkit/AFKListenerTest.java new file mode 100644 index 000000000..3461abcf4 --- /dev/null +++ b/Plan/src/test/java/com/djrapitops/plan/system/listeners/bukkit/AFKListenerTest.java @@ -0,0 +1,67 @@ +package com.djrapitops.plan.system.listeners.bukkit; + +import com.djrapitops.plan.system.settings.Settings; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerMoveEvent; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; +import utilities.Teardown; +import utilities.TestConstants; + +import java.util.ArrayList; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; + +/** + * Test for {@link AFKListener} + * + * @author Rsl1122 + */ +public class AFKListenerTest { + + @BeforeClass + public static void setUpClass() { + Settings.AFK_THRESHOLD_MINUTES.setTemporaryValue(3); + } + + @AfterClass + public static void tearDownClass() { + Teardown.resetSettingsTempValues(); + } + + @Test + public void testAfkPermissionCallCaching() { + AFKListener afkListener = new AFKListener(); + Collection calls = new ArrayList<>(); + + Player player = mockPlayer(calls); + PlayerMoveEvent event = mockMoveEvent(player); + + afkListener.onMove(event); + assertEquals(1, calls.size()); + afkListener.onMove(event); + assertEquals(1, calls.size()); + } + + private PlayerMoveEvent mockMoveEvent(Player player) { + PlayerMoveEvent event = Mockito.mock(PlayerMoveEvent.class); + doReturn(player).when(event).getPlayer(); + return event; + } + + private Player mockPlayer(Collection calls) { + Player player = Mockito.mock(Player.class); + doReturn(TestConstants.PLAYER_ONE_UUID).when(player).getUniqueId(); + doAnswer(perm -> { + calls.add(true); + return true; + }).when(player).hasPermission(Mockito.anyString()); + return player; + } + +} \ No newline at end of file From 0bb2922329eff32828547544a34e6be37db54fe2 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 30 Aug 2018 17:22:33 +0300 Subject: [PATCH 38/76] Fixed #693 --- .../djrapitops/plan/system/listeners/bukkit/AFKListener.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/AFKListener.java b/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/AFKListener.java index 6f1246cc6..ac7d9ec0d 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/AFKListener.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/AFKListener.java @@ -41,7 +41,10 @@ public class AFKListener implements Listener { UUID uuid = player.getUniqueId(); long time = System.currentTimeMillis(); - boolean ignored = ignorePermissionInfo.getOrDefault(uuid, player.hasPermission(Permissions.IGNORE_AFK.getPermission())); + Boolean ignored = ignorePermissionInfo.get(uuid); + if (ignored == null) { + ignored = player.hasPermission(Permissions.IGNORE_AFK.getPermission()); + } if (ignored) { AFK_TRACKER.hasIgnorePermission(uuid); ignorePermissionInfo.put(uuid, true); From be2be530cab4e7a51caa7c3d81205af952c11d48 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 30 Aug 2018 17:43:43 +0300 Subject: [PATCH 39/76] Reproduced #710 with a test --- .../TimeAmountFormatterExtraZerosTest.java | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterExtraZerosTest.java diff --git a/Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterExtraZerosTest.java b/Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterExtraZerosTest.java new file mode 100644 index 000000000..2b1e7809b --- /dev/null +++ b/Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterExtraZerosTest.java @@ -0,0 +1,130 @@ +package com.djrapitops.plan.data.store.mutators.formatting; + +import com.djrapitops.plan.system.settings.Settings; +import com.djrapitops.plugin.api.TimeAmount; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import utilities.Teardown; + +import static org.junit.Assert.assertEquals; + +/** + * Test class for {@link TimeAmountFormatter} that checks extra zeros config example. + * + * @author Rsl1122 + */ +public class TimeAmountFormatterExtraZerosTest { + + private TimeAmountFormatter timeAmountFormatter; + + @BeforeClass + public static void setUpClass() { + Settings.FORMAT_YEAR.setTemporaryValue("1 year, "); + Settings.FORMAT_YEARS.setTemporaryValue("%years% years, "); + Settings.FORMAT_MONTH.setTemporaryValue("1 month, "); + Settings.FORMAT_MONTHS.setTemporaryValue("%months% months, "); + Settings.FORMAT_DAY.setTemporaryValue("1d "); + Settings.FORMAT_DAYS.setTemporaryValue("%days%d "); + Settings.FORMAT_HOURS.setTemporaryValue("%zero%%hours%:"); + Settings.FORMAT_MINUTES.setTemporaryValue("%hours%%zero%%minutes%:"); + Settings.FORMAT_SECONDS.setTemporaryValue("%minutes%%zero%%seconds%"); + Settings.FORMAT_ZERO_SECONDS.setTemporaryValue("00:00:00"); + } + + @AfterClass + public static void tearDownClass() { + Teardown.resetSettingsTempValues(); + } + + @Before + public void setUp() { + timeAmountFormatter = new TimeAmountFormatter(); + } + + @Test + public void exampleOne() { + String expected = "1 year, 1 month, 5d 12:30:20"; + + long ms = TimeAmount.DAY.ms() * 400L + + TimeAmount.HOUR.ms() * 12L + + TimeAmount.MINUTE.ms() * 30L + + TimeAmount.SECOND.ms() * 20L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleTwo() { + String expected = "1 year, 1 month, 5d "; + + long ms = TimeAmount.DAY.ms() * 400L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleThree() { + String expected = "12:00:20"; + + long ms = TimeAmount.HOUR.ms() * 12L + + TimeAmount.SECOND.ms() * 20L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleFour() { + String expected = "00:30:00"; + + long ms = TimeAmount.MINUTE.ms() * 30L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleFive() { + String expected = "00:00:20"; + + long ms = TimeAmount.SECOND.ms() * 20L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleZero() { + String expected = "-"; + + long ms = 0L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleOneSecond() { + String expected = "00:00:01"; + + long ms = TimeAmount.SECOND.ms(); + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleOneMinute() { + String expected = "00:01:00"; + + long ms = TimeAmount.MINUTE.ms(); + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + +} \ No newline at end of file From bee9966450e458bc6cc622aa8975d148352b9dc0 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 30 Aug 2018 17:52:50 +0300 Subject: [PATCH 40/76] Ignored exception in #715 --- .../formatting/TimeAmountFormatter.java | 34 +++++++++---------- .../system/tasks/LogsFolderCleanTask.java | 6 +++- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatter.java b/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatter.java index e758a55cf..51471b492 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatter.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatter.java @@ -57,20 +57,13 @@ public class TimeAmountFormatter implements Formatter { return formattedTime; } - private void appendSeconds(StringBuilder builder, long seconds, long minutes, long hours, String fHours, String fMinutes, String fSeconds) { - if (seconds != 0) { - String s = fSeconds.replace(SECONDS_PH, String.valueOf(seconds)); - if (minutes == 0 && s.contains(MINUTES_PH)) { - if (hours == 0 && fMinutes.contains(HOURS_PH)) { - builder.append(fHours.replace(ZERO_PH, "0").replace(HOURS_PH, "0")); - } - builder.append(fMinutes.replace(HOURS_PH, "").replace(ZERO_PH, "0").replace(MINUTES_PH, "0")); - } - s = s.replace(MINUTES_PH, ""); - if (s.contains(ZERO_PH) && String.valueOf(seconds).length() == 1) { + private void appendHours(StringBuilder builder, long hours, String fHours) { + if (hours != 0) { + String h = fHours.replace(HOURS_PH, String.valueOf(hours)); + if (h.contains(ZERO_PH) && String.valueOf(hours).length() == 1) { builder.append('0'); } - builder.append(s); + builder.append(h); } } @@ -89,13 +82,20 @@ public class TimeAmountFormatter implements Formatter { } } - private void appendHours(StringBuilder builder, long hours, String fHours) { - if (hours != 0) { - String h = fHours.replace(HOURS_PH, String.valueOf(hours)); - if (h.contains(ZERO_PH) && String.valueOf(hours).length() == 1) { + private void appendSeconds(StringBuilder builder, long seconds, long minutes, long hours, String fHours, String fMinutes, String fSeconds) { + if (seconds != 0) { + String s = fSeconds.replace(SECONDS_PH, String.valueOf(seconds)); + if (minutes == 0 && s.contains(MINUTES_PH)) { + if (hours == 0 && fMinutes.contains(HOURS_PH)) { + builder.append(fHours.replace(ZERO_PH, "0").replace(HOURS_PH, "0")); + } + builder.append(fMinutes.replace(HOURS_PH, "").replace(ZERO_PH, "0").replace(MINUTES_PH, "0")); + } + s = s.replace(MINUTES_PH, ""); + if (s.contains(ZERO_PH) && String.valueOf(seconds).length() == 1) { builder.append('0'); } - builder.append(h); + builder.append(s); } } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/LogsFolderCleanTask.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/LogsFolderCleanTask.java index 1bdd02b12..a8bf2bbf5 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/LogsFolderCleanTask.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/LogsFolderCleanTask.java @@ -34,7 +34,11 @@ public class LogsFolderCleanTask extends AbsRunnable { } catch (NullPointerException ignore) { /* Ignored - not supposed to occur. */ } finally { - cancel(); + try { + cancel(); + } catch (Exception ignore) { + /* Ignored, TaskCenter concurrent modification exception, will be fixed later in apf-3.3.0. */ + } } } From 08d1067bd8c1dcf050fde64389e90703bfcdd258 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 30 Aug 2018 18:02:24 +0300 Subject: [PATCH 41/76] Fixed #710 --- .../formatting/TimeAmountFormatter.java | 2 +- .../TimeAmountFormatterDefaultTest.java | 130 ++++++++++++++++++ .../TimeAmountFormatterExtraZerosTest.java | 2 +- 3 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterDefaultTest.java diff --git a/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatter.java b/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatter.java index 51471b492..014309a7b 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatter.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatter.java @@ -83,7 +83,7 @@ public class TimeAmountFormatter implements Formatter { } private void appendSeconds(StringBuilder builder, long seconds, long minutes, long hours, String fHours, String fMinutes, String fSeconds) { - if (seconds != 0) { + if (seconds != 0 || fSeconds.contains(ZERO_PH)) { String s = fSeconds.replace(SECONDS_PH, String.valueOf(seconds)); if (minutes == 0 && s.contains(MINUTES_PH)) { if (hours == 0 && fMinutes.contains(HOURS_PH)) { diff --git a/Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterDefaultTest.java b/Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterDefaultTest.java new file mode 100644 index 000000000..236388d4d --- /dev/null +++ b/Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterDefaultTest.java @@ -0,0 +1,130 @@ +package com.djrapitops.plan.data.store.mutators.formatting; + +import com.djrapitops.plan.system.settings.Settings; +import com.djrapitops.plugin.api.TimeAmount; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import utilities.Teardown; + +import static org.junit.Assert.assertEquals; + +/** + * Test class for {@link TimeAmountFormatter} that checks extra zeros config example. + * + * @author Rsl1122 + */ +public class TimeAmountFormatterDefaultTest { + + private TimeAmountFormatter timeAmountFormatter; + + @BeforeClass + public static void setUpClass() { + Settings.FORMAT_YEAR.setTemporaryValue("1 year, "); + Settings.FORMAT_YEARS.setTemporaryValue("%years% years, "); + Settings.FORMAT_MONTH.setTemporaryValue("1 month, "); + Settings.FORMAT_MONTHS.setTemporaryValue("%months% months, "); + Settings.FORMAT_DAY.setTemporaryValue("1d "); + Settings.FORMAT_DAYS.setTemporaryValue("%days%d "); + Settings.FORMAT_HOURS.setTemporaryValue("%hours%h "); + Settings.FORMAT_MINUTES.setTemporaryValue("%minutes%m "); + Settings.FORMAT_SECONDS.setTemporaryValue("%seconds%s"); + Settings.FORMAT_ZERO_SECONDS.setTemporaryValue("0s"); + } + + @AfterClass + public static void tearDownClass() { + Teardown.resetSettingsTempValues(); + } + + @Before + public void setUp() { + timeAmountFormatter = new TimeAmountFormatter(); + } + + @Test + public void exampleOne() { + String expected = "1 year, 1 month, 5d 12h 30m 20s"; + + long ms = TimeAmount.DAY.ms() * 400L + + TimeAmount.HOUR.ms() * 12L + + TimeAmount.MINUTE.ms() * 30L + + TimeAmount.SECOND.ms() * 20L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleTwo() { + String expected = "1 year, 1 month, 5d "; + + long ms = TimeAmount.DAY.ms() * 400L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleThree() { + String expected = "12h 20s"; + + long ms = TimeAmount.HOUR.ms() * 12L + + TimeAmount.SECOND.ms() * 20L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleFour() { + String expected = "30m "; + + long ms = TimeAmount.MINUTE.ms() * 30L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleFive() { + String expected = "20s"; + + long ms = TimeAmount.SECOND.ms() * 20L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleZero() { + String expected = "-"; + + long ms = 0L; + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleOneSecond() { + String expected = "1s"; + + long ms = TimeAmount.SECOND.ms(); + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + + @Test + public void exampleOneMinute() { + String expected = "1m "; + + long ms = TimeAmount.MINUTE.ms(); + String result = timeAmountFormatter.apply(ms); + + assertEquals(expected, result); + } + +} \ No newline at end of file diff --git a/Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterExtraZerosTest.java b/Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterExtraZerosTest.java index 2b1e7809b..7971d5d9f 100644 --- a/Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterExtraZerosTest.java +++ b/Plan/src/test/java/com/djrapitops/plan/data/store/mutators/formatting/TimeAmountFormatterExtraZerosTest.java @@ -58,7 +58,7 @@ public class TimeAmountFormatterExtraZerosTest { @Test public void exampleTwo() { - String expected = "1 year, 1 month, 5d "; + String expected = "1 year, 1 month, 5d 00:00:00"; long ms = TimeAmount.DAY.ms() * 400L; String result = timeAmountFormatter.apply(ms); From 333936cfa8682ac3dd3d88cdc01d319293ba6c9e Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Thu, 30 Aug 2018 18:10:16 +0300 Subject: [PATCH 42/76] Implemented afk check on kick event #705 Note about AFK ignore perm: - Players kicked by afk that have plan.ignore.afk will be counted as "real" kicks. --- .../com/djrapitops/plan/system/afk/AFKTracker.java | 10 ++++++++++ .../system/listeners/bukkit/PlayerOnlineListener.java | 4 ++++ .../system/listeners/sponge/SpongePlayerListener.java | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/afk/AFKTracker.java b/Plan/src/main/java/com/djrapitops/plan/system/afk/AFKTracker.java index d9c7f85a6..0dd7a512e 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/afk/AFKTracker.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/afk/AFKTracker.java @@ -65,4 +65,14 @@ public class AFKTracker { lastMovement.remove(uuid); usedAFKCommand.remove(uuid); } + + public boolean isAfk(UUID uuid) { + long time = System.currentTimeMillis(); + + Long lastMoved = lastMovement.get(uuid); + if (lastMoved == null || lastMoved == -1) { + return false; + } + return time - lastMoved > afkThresholdMs; + } } \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/PlayerOnlineListener.java b/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/PlayerOnlineListener.java index c28f0f4a8..5daaf4d84 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/PlayerOnlineListener.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/listeners/bukkit/PlayerOnlineListener.java @@ -63,6 +63,10 @@ public class PlayerOnlineListener implements Listener { return; } UUID uuid = event.getPlayer().getUniqueId(); + if (AFKListener.AFK_TRACKER.isAfk(uuid)) { + return; + } + Processing.submit(new KickProcessor(uuid)); } catch (Exception e) { Log.toLog(this.getClass(), e); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/listeners/sponge/SpongePlayerListener.java b/Plan/src/main/java/com/djrapitops/plan/system/listeners/sponge/SpongePlayerListener.java index d0f4c478c..c7f5897f5 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/listeners/sponge/SpongePlayerListener.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/listeners/sponge/SpongePlayerListener.java @@ -2,6 +2,7 @@ package com.djrapitops.plan.system.listeners.sponge; import com.djrapitops.plan.data.container.Session; import com.djrapitops.plan.system.cache.SessionCache; +import com.djrapitops.plan.system.listeners.bukkit.AFKListener; import com.djrapitops.plan.system.processing.Processing; import com.djrapitops.plan.system.processing.processors.info.NetworkPageUpdateProcessor; import com.djrapitops.plan.system.processing.processors.info.PlayerPageUpdateProcessor; @@ -52,6 +53,9 @@ public class SpongePlayerListener { public void onKick(KickPlayerEvent event) { try { UUID uuid = event.getTargetEntity().getUniqueId(); + if (AFKListener.AFK_TRACKER.isAfk(uuid)) { + return; + } Processing.submit(new KickProcessor(uuid)); } catch (Exception e) { Log.toLog(this.getClass(), e); From 8a4b23649d205454ef045ed1c6b9e9dc8f5e0e0a Mon Sep 17 00:00:00 2001 From: BrainStone Date: Fri, 31 Aug 2018 18:47:04 +0200 Subject: [PATCH 43/76] PingCountTimerSponge --- .../plan/system/tasks/BukkitTaskSystem.java | 6 +- .../plan/system/tasks/SpongeTaskSystem.java | 18 +++ ...ntTimer.java => PingCountTimerBukkit.java} | 4 +- .../tasks/server/PingCountTimerSponge.java | 110 ++++++++++++++++++ 4 files changed, 133 insertions(+), 5 deletions(-) rename Plan/src/main/java/com/djrapitops/plan/system/tasks/server/{PingCountTimer.java => PingCountTimerBukkit.java} (97%) create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerSponge.java diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java index b99dde0c8..1a769c767 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/BukkitTaskSystem.java @@ -8,7 +8,7 @@ import com.djrapitops.plan.Plan; import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.system.tasks.server.BukkitTPSCountTimer; import com.djrapitops.plan.system.tasks.server.PaperTPSCountTimer; -import com.djrapitops.plan.system.tasks.server.PingCountTimer; +import com.djrapitops.plan.system.tasks.server.PingCountTimerBukkit; import com.djrapitops.plugin.api.Check; import com.djrapitops.plugin.api.TimeAmount; import com.djrapitops.plugin.task.RunnableFactory; @@ -33,11 +33,11 @@ public class BukkitTaskSystem extends ServerTaskSystem { public void enable() { super.enable(); try { - PingCountTimer pingCountTimer = new PingCountTimer(); + PingCountTimerBukkit pingCountTimer = new PingCountTimerBukkit(); ((Plan) plugin).registerListener(pingCountTimer); long startDelay = TimeAmount.SECOND.ticks() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); RunnableFactory.createNew("PingCountTimer", pingCountTimer) - .runTaskTimer(startDelay, PingCountTimer.PING_INTERVAL); + .runTaskTimer(startDelay, PingCountTimerBukkit.PING_INTERVAL); } catch (ExceptionInInitializerError | NoClassDefFoundError ignore) { // Running CraftBukkit } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java index 7b0017108..9b13ed881 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java @@ -1,7 +1,11 @@ package com.djrapitops.plan.system.tasks; import com.djrapitops.plan.PlanSponge; +import com.djrapitops.plan.system.settings.Settings; +import com.djrapitops.plan.system.tasks.server.PingCountTimerSponge; import com.djrapitops.plan.system.tasks.server.SpongeTPSCountTimer; +import com.djrapitops.plugin.api.TimeAmount; +import com.djrapitops.plugin.task.RunnableFactory; import org.spongepowered.api.Sponge; import org.spongepowered.api.scheduler.Task; @@ -10,6 +14,20 @@ public class SpongeTaskSystem extends ServerTaskSystem { public SpongeTaskSystem(PlanSponge plugin) { super(plugin, new SpongeTPSCountTimer(plugin)); } + + @Override + public void enable() { + super.enable(); + try { + PingCountTimerSponge pingCountTimer = new PingCountTimerSponge(); + Sponge.getEventManager().registerListeners(plugin, pingCountTimer); + long startDelay = TimeAmount.SECOND.ticks() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); + RunnableFactory.createNew("PingCountTimer", pingCountTimer) + .runTaskTimer(startDelay, PingCountTimerSponge.PING_INTERVAL); + } catch (ExceptionInInitializerError | NoClassDefFoundError ignore) { + // Running CraftBukkit + } + } @Override public void disable() { diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimer.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBukkit.java similarity index 97% rename from Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimer.java rename to Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBukkit.java index 37c0b728b..97c818546 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimer.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBukkit.java @@ -53,7 +53,7 @@ import java.util.*; * * @author games647 */ -public class PingCountTimer extends AbsRunnable implements Listener { +public class PingCountTimerBukkit extends AbsRunnable implements Listener { //the server is pinging the client every 40 Ticks (2 sec) - so check it then //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/PlayerConnection.java#L178 @@ -80,7 +80,7 @@ public class PingCountTimer extends AbsRunnable implements Listener { localPing = lookup.findGetter(entityPlayer, "ping", Integer.TYPE); } catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException reflectiveEx) { - Log.toLog(PingCountTimer.class, reflectiveEx); + Log.toLog(PingCountTimerBukkit.class, reflectiveEx); } } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerSponge.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerSponge.java new file mode 100644 index 000000000..cfed96452 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerSponge.java @@ -0,0 +1,110 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.djrapitops.plan.system.tasks.server; + +import com.djrapitops.plan.data.store.objects.DateObj; +import com.djrapitops.plan.system.processing.Processing; +import com.djrapitops.plan.system.processing.processors.player.PingInsertProcessor; +import com.djrapitops.plan.system.settings.Settings; +import com.djrapitops.plugin.api.TimeAmount; +import com.djrapitops.plugin.task.AbsRunnable; +import com.djrapitops.plugin.task.RunnableFactory; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.network.ClientConnectionEvent; + +import java.util.*; + +/** + * Task that handles player ping calculation on Sponge based servers. + * + * @author BrainStone + */ +public class PingCountTimerSponge extends AbsRunnable { + + //the server is pinging the client every 40 Ticks (2 sec) - so check it then + //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/PlayerConnection.java#L178 + public static final int PING_INTERVAL = 2 * 20; + + private final Map>> playerHistory = new HashMap<>(); + + @Override + public void run() { + List loggedOut = new ArrayList<>(); + long time = System.currentTimeMillis(); + playerHistory.forEach((uuid, history) -> { + Optional player = Sponge.getServer().getPlayer(uuid); + if (player.isPresent()) { + int ping = getPing(player.get()); + if (ping < -1 || ping > TimeAmount.SECOND.ms() * 8L) { + // Don't accept bad values + return; + } + history.add(new DateObj<>(time, ping)); + if (history.size() >= 30) { + Processing.submit(new PingInsertProcessor(uuid, new ArrayList<>(history))); + history.clear(); + } + } else { + loggedOut.add(uuid); + } + }); + loggedOut.forEach(playerHistory::remove); + } + + public void addPlayer(Player player) { + playerHistory.put(player.getUniqueId(), new ArrayList<>()); + } + + public void removePlayer(Player player) { + playerHistory.remove(player.getUniqueId()); + } + + private int getPing(Player player) { + return player.getConnection().getLatency(); + } + + @Listener + public void onPlayerJoin(ClientConnectionEvent.Join joinEvent) { + Player player = joinEvent.getTargetEntity(); + RunnableFactory.createNew("Add Player to Ping list", new AbsRunnable() { + @Override + public void run() { + if (player.isOnline()) { + addPlayer(player); + } + } + }).runTaskLater(TimeAmount.SECOND.ticks() * (long) Settings.PING_PLAYER_LOGIN_DELAY.getNumber()); + } + + @Listener + public void onPlayerQuit(ClientConnectionEvent.Disconnect quitEvent) { + removePlayer(quitEvent.getTargetEntity()); + } + + public void clear() { + playerHistory.clear(); + } +} From 81992a5676b2209a81941804f1837eebdd76e159 Mon Sep 17 00:00:00 2001 From: BrainStone Date: Sat, 1 Sep 2018 18:01:10 +0200 Subject: [PATCH 44/76] Using correct way to register listener --- .../plan/system/tasks/SpongeTaskSystem.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java index 9b13ed881..05ab2d0f7 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java @@ -18,15 +18,11 @@ public class SpongeTaskSystem extends ServerTaskSystem { @Override public void enable() { super.enable(); - try { - PingCountTimerSponge pingCountTimer = new PingCountTimerSponge(); - Sponge.getEventManager().registerListeners(plugin, pingCountTimer); - long startDelay = TimeAmount.SECOND.ticks() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); - RunnableFactory.createNew("PingCountTimer", pingCountTimer) - .runTaskTimer(startDelay, PingCountTimerSponge.PING_INTERVAL); - } catch (ExceptionInInitializerError | NoClassDefFoundError ignore) { - // Running CraftBukkit - } + PingCountTimerSponge pingCountTimer = new PingCountTimerSponge(); + ((PlanSponge) plugin).registerListener(pingCountTimer); + long startDelay = TimeAmount.SECOND.ticks() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); + RunnableFactory.createNew("PingCountTimer", pingCountTimer) + .runTaskTimer(startDelay, PingCountTimerSponge.PING_INTERVAL); } @Override From 7cce16813397f196e48be3c0c045ff26a0ab85ab Mon Sep 17 00:00:00 2001 From: BrainStone Date: Sat, 1 Sep 2018 18:15:07 +0200 Subject: [PATCH 45/76] No need for a cast --- .../com/djrapitops/plan/system/tasks/SpongeTaskSystem.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java index 05ab2d0f7..4865ffcf9 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java @@ -10,16 +10,19 @@ import org.spongepowered.api.Sponge; import org.spongepowered.api.scheduler.Task; public class SpongeTaskSystem extends ServerTaskSystem { + + private final PlanSponge plugin; public SpongeTaskSystem(PlanSponge plugin) { super(plugin, new SpongeTPSCountTimer(plugin)); + this.plugin = plugin; } @Override public void enable() { super.enable(); PingCountTimerSponge pingCountTimer = new PingCountTimerSponge(); - ((PlanSponge) plugin).registerListener(pingCountTimer); + plugin.registerListener(pingCountTimer); long startDelay = TimeAmount.SECOND.ticks() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); RunnableFactory.createNew("PingCountTimer", pingCountTimer) .runTaskTimer(startDelay, PingCountTimerSponge.PING_INTERVAL); From 00cdd6e251a96544da5f9cb3ee7e5d9fb9fb84b3 Mon Sep 17 00:00:00 2001 From: Yannick Schinko Date: Sat, 1 Sep 2018 21:59:09 +0200 Subject: [PATCH 46/76] Bungee pingAdded PinCoutTimerBungee (#717) * Implemented Bungee ping measurement * Removed unused import * Removed second unused import --- .../plan/system/tasks/BungeeTaskSystem.java | 7 ++ .../tasks/server/PingCountTimerBungee.java | 111 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBungee.java diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/BungeeTaskSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/BungeeTaskSystem.java index ad6506e46..a5cf91ab2 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/BungeeTaskSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/BungeeTaskSystem.java @@ -9,8 +9,10 @@ import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.system.tasks.bungee.BungeeTPSCountTimer; import com.djrapitops.plan.system.tasks.bungee.EnableConnectionTask; import com.djrapitops.plan.system.tasks.server.NetworkPageRefreshTask; +import com.djrapitops.plan.system.tasks.server.PingCountTimerBungee; import com.djrapitops.plan.utilities.file.export.HtmlExport; import com.djrapitops.plugin.api.TimeAmount; +import com.djrapitops.plugin.task.RunnableFactory; /** * TaskSystem responsible for registering tasks for Bungee. @@ -38,5 +40,10 @@ public class BungeeTaskSystem extends TaskSystem { if (Settings.ANALYSIS_EXPORT.isTrue()) { registerTask(new HtmlExport(plugin)).runTaskAsynchronously(); } + PingCountTimerBungee pingCountTimer = new PingCountTimerBungee(); + plugin.registerListener(pingCountTimer); + long startDelay = TimeAmount.SECOND.ticks() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); + RunnableFactory.createNew("PingCountTimer", pingCountTimer) + .runTaskTimer(startDelay, PingCountTimerBungee.PING_INTERVAL); } } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBungee.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBungee.java new file mode 100644 index 000000000..cb24e2aa5 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBungee.java @@ -0,0 +1,111 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.djrapitops.plan.system.tasks.server; + +import com.djrapitops.plan.data.store.objects.DateObj; +import com.djrapitops.plan.system.processing.Processing; +import com.djrapitops.plan.system.processing.processors.player.PingInsertProcessor; +import com.djrapitops.plan.system.settings.Settings; +import com.djrapitops.plugin.api.TimeAmount; +import com.djrapitops.plugin.task.AbsRunnable; +import com.djrapitops.plugin.task.RunnableFactory; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.ServerConnectedEvent; +import net.md_5.bungee.api.event.ServerDisconnectEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; +import java.util.*; + +/** + * Task that handles player ping calculation on Bungee based servers. + * + * @author BrainStone + */ +public class PingCountTimerBungee extends AbsRunnable implements Listener { + + //the server is pinging the client every 40 Ticks (2 sec) - so check it then + //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/PlayerConnection.java#L178 + public static final int PING_INTERVAL = 2 * 20; + + private final Map>> playerHistory = new HashMap<>(); + + @Override + public void run() { + List loggedOut = new ArrayList<>(); + long time = System.currentTimeMillis(); + playerHistory.forEach((uuid, history) -> { + ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid); + if (player != null) { + int ping = getPing(player); + if (ping < -1 || ping > TimeAmount.SECOND.ms() * 8L) { + // Don't accept bad values + return; + } + history.add(new DateObj<>(time, ping)); + if (history.size() >= 30) { + Processing.submit(new PingInsertProcessor(uuid, new ArrayList<>(history))); + history.clear(); + } + } else { + loggedOut.add(uuid); + } + }); + loggedOut.forEach(playerHistory::remove); + } + + public void addPlayer(ProxiedPlayer player) { + playerHistory.put(player.getUniqueId(), new ArrayList<>()); + } + + public void removePlayer(ProxiedPlayer player) { + playerHistory.remove(player.getUniqueId()); + } + + private int getPing(ProxiedPlayer player) { + return player.getPing(); + } + + @EventHandler + public void onPlayerJoin(ServerConnectedEvent joinEvent) { + ProxiedPlayer player = joinEvent.getPlayer(); + RunnableFactory.createNew("Add Player to Ping list", new AbsRunnable() { + @Override + public void run() { + if (player.isConnected()) { + addPlayer(player); + } + } + }).runTaskLater(TimeAmount.SECOND.ticks() * (long) Settings.PING_PLAYER_LOGIN_DELAY.getNumber()); + } + + @EventHandler + public void onPlayerQuit(ServerDisconnectEvent quitEvent) { + removePlayer(quitEvent.getPlayer()); + } + + public void clear() { + playerHistory.clear(); + } +} From 2783841e4b0d527d0af78ce5b6ed91c45819e260 Mon Sep 17 00:00:00 2001 From: Vankka Date: Wed, 5 Sep 2018 20:22:35 +0300 Subject: [PATCH 47/76] Nucleus PluginData (#723) Implements Nucleus PluginData in #583 --- PlanPluginBridge/pom.xml | 21 ++ .../djrapitops/pluginbridge/plan/Bridge.java | 4 +- .../plan/nucleus/NucleusData.java | 290 ++++++++++++++++++ .../plan/nucleus/NucleusHook.java | 26 ++ 4 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/nucleus/NucleusData.java create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/nucleus/NucleusHook.java diff --git a/PlanPluginBridge/pom.xml b/PlanPluginBridge/pom.xml index eb7c024dc..a4f68f61d 100644 --- a/PlanPluginBridge/pom.xml +++ b/PlanPluginBridge/pom.xml @@ -53,6 +53,10 @@ paper-repo https://repo.destroystokyo.com/repository/maven-public/ + + sponge-repo + https://repo.spongepowered.org/maven + vault-repo http://nexus.hc.to/content/repositories/pub_releases @@ -77,6 +81,10 @@ advanced-achievements-repo https://raw.github.com/PyvesB/AdvancedAchievements/mvn-repo/ + + nucleus-repo + http://repo.drnaylor.co.uk/artifactory/list/minecraft + @@ -108,6 +116,13 @@ jar provided + + org.spongepowered + spongeapi + 7.0.0 + jar + provided + @@ -146,6 +161,12 @@ 1.5.0 provided + + io.github.nucleuspowered + nucleus-api + 1.6.0-PR1-S7.0 + provided + diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java index 6659fdb50..cdc1e6ce9 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java @@ -19,6 +19,7 @@ import com.djrapitops.pluginbridge.plan.kingdoms.KingdomsHook; import com.djrapitops.pluginbridge.plan.litebans.LiteBansBukkitHook; import com.djrapitops.pluginbridge.plan.litebans.LiteBansBungeeHook; import com.djrapitops.pluginbridge.plan.mcmmo.McmmoHook; +import com.djrapitops.pluginbridge.plan.nucleus.NucleusHook; import com.djrapitops.pluginbridge.plan.protocolsupport.ProtocolSupportHook; import com.djrapitops.pluginbridge.plan.redprotect.RedProtectHook; import com.djrapitops.pluginbridge.plan.superbvote.SuperbVoteHook; @@ -72,7 +73,8 @@ public class Bridge { private static Hook[] getSpongeHooks(HookHandler h) { return new Hook[]{ - new BuyCraftHook(h) + new BuyCraftHook(h), + new NucleusHook(h) }; } diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/nucleus/NucleusData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/nucleus/NucleusData.java new file mode 100644 index 000000000..02cc603e5 --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/nucleus/NucleusData.java @@ -0,0 +1,290 @@ +/* + * Licence is provided in the jar as license.yml also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml + */ +package com.djrapitops.pluginbridge.plan.nucleus; + +import com.djrapitops.plan.api.PlanAPI; +import com.djrapitops.plan.data.element.AnalysisContainer; +import com.djrapitops.plan.data.element.InspectContainer; +import com.djrapitops.plan.data.element.TableContainer; +import com.djrapitops.plan.data.plugin.ContainerSize; +import com.djrapitops.plan.data.plugin.PluginData; +import com.djrapitops.plan.utilities.FormatUtils; +import com.djrapitops.plan.utilities.html.Html; +import com.djrapitops.plan.utilities.html.HtmlUtils; +import com.djrapitops.plan.utilities.html.icon.Color; +import com.djrapitops.plan.utilities.html.icon.Family; +import com.djrapitops.plan.utilities.html.icon.Icon; +import io.github.nucleuspowered.nucleus.api.NucleusAPI; +import io.github.nucleuspowered.nucleus.api.nucleusdata.*; +import io.github.nucleuspowered.nucleus.api.service.*; +import java.time.Duration; +import java.time.Instant; +import java.util.*; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.service.user.UserStorageService; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.text.serializer.TextSerializers; + +/** + * PluginData for Nucleus plugin. + * + * @author Vankka + */ +public class NucleusData extends PluginData { + private UserStorageService userStorageService = null; + + public NucleusData() { + super(ContainerSize.TWO_THIRDS, "Nucleus"); + setPluginIcon(Icon.called("flask").of(Color.DEEP_ORANGE).build()); + + Sponge.getServiceManager().provide(UserStorageService.class).ifPresent(storageService -> userStorageService = storageService); + } + + @Override + public InspectContainer getPlayerData(UUID uuid, InspectContainer inspectContainer) { + User user = getUser(uuid); + + if (user == null) { + inspectContainer.addValue("Data unavailable", "Could not get user data"); + return inspectContainer; + } + + NucleusAPI.getMuteService().ifPresent(muteService -> addMuteData(user, muteService, inspectContainer)); + NucleusAPI.getJailService().ifPresent(jailService -> addJailData(user, jailService, inspectContainer)); + NucleusAPI.getHomeService().ifPresent(homeService -> addHomeData(user, homeService, inspectContainer)); + NucleusAPI.getNoteService().ifPresent(noteService -> addNoteData(user, noteService, inspectContainer)); + NucleusAPI.getWarningService().ifPresent(warningService -> addWarningData(user, warningService, inspectContainer)); + NucleusAPI.getInvulnerabilityService().ifPresent(invulnerabilityService -> addInvulnerabilityData(user, invulnerabilityService, inspectContainer)); + NucleusAPI.getNicknameService().ifPresent(nicknameService -> addNicknameData(user, nicknameService, inspectContainer)); + + return inspectContainer; + } + + @Override + public AnalysisContainer getServerData(Collection uuids, AnalysisContainer analysisContainer) { + NucleusAPI.getWarpService().ifPresent(warpService -> addWarpData(warpService, analysisContainer)); + NucleusAPI.getJailService().ifPresent(jailService -> addJailData(jailService, analysisContainer)); + NucleusAPI.getKitService().ifPresent(kitService -> addKitData(kitService, analysisContainer)); + + return analysisContainer; + } + + private User getUser(UUID uuid) { + if (Sponge.getServer().getPlayer(uuid).isPresent()) { + return Sponge.getServer().getPlayer(uuid).get(); + } else if (userStorageService != null) { + Optional optionalUser = userStorageService.get(uuid); + return optionalUser.orElse(null); + } else { + return null; + } + } + + private String formatTimeStampYear(Instant instant) { + return FormatUtils.formatTimeStampYear(instant.toEpochMilli()); + } + + private String formatTimeStampYear(Duration duration) { + return FormatUtils.formatTimeStampYear(duration.plusMillis(System.currentTimeMillis()).toMillis()); + } + + /* + * Player Data + */ + private void addMuteData(User user, NucleusMuteService muteService, InspectContainer inspectContainer) { + boolean muted = muteService.isMuted(user); + inspectContainer.addValue(getWithIcon("Muted", Icon.called("bell-slash").of(Color.DEEP_ORANGE)), muted ? "Yes" : "No"); + + Optional optionalMuteInfo = muteService.getPlayerMuteInfo(user); + if (muted && optionalMuteInfo.isPresent()) { + MuteInfo muteInfo = optionalMuteInfo.get(); + + String reason = HtmlUtils.swapColorsToSpan(muteInfo.getReason()); + String start = muteInfo.getCreationInstant().map(this::formatTimeStampYear).orElse("Unknown"); + String end = muteInfo.getRemainingTime().map(this::formatTimeStampYear).orElse("Permanent mute"); + String link = "Unknown"; + + User operatorUser = muteInfo.getMuter().map(this::getUser).orElse(null); + if (operatorUser != null) { + String operator = operatorUser.getName(); + link = Html.LINK.parse(PlanAPI.getInstance().getPlayerInspectPageLink(operator), operator); + } + + inspectContainer.addValue(" " + getWithIcon("Operator", Icon.called("user").of(Color.DEEP_ORANGE)), link); + inspectContainer.addValue(" " + getWithIcon("Date", Icon.called("calendar").of(Color.DEEP_ORANGE).of(Family.REGULAR)), start); + inspectContainer.addValue(" " + getWithIcon("Ends", Icon.called("calendar-check").of(Color.DEEP_ORANGE).of(Family.REGULAR)), end); + inspectContainer.addValue(" " + getWithIcon("Reason", Icon.called("comment").of(Color.DEEP_ORANGE).of(Family.REGULAR)), reason); + } + } + + private void addJailData(User user, NucleusJailService jailService, InspectContainer inspectContainer) { + boolean jailed = jailService.isPlayerJailed(user); + inspectContainer.addValue(getWithIcon("Jailed", Icon.called("bars").of(Color.YELLOW).of(Family.SOLID)), jailed ? "Yes" : "No"); + + if (jailed && jailService.getPlayerJailData(user).isPresent()) { + Inmate inmate = jailService.getPlayerJailData(user).get(); + + String reason = inmate.getReason(); + String start = inmate.getCreationInstant().map(this::formatTimeStampYear).orElse("Unknown"); + String end = inmate.getRemainingTime().map(this::formatTimeStampYear).orElse("Permanent jail sentence"); + String link = "Unknown"; + + User operatorUser = inmate.getJailer().map(this::getUser).orElse(null); + if (operatorUser != null) { + String operator = operatorUser.getName(); + link = Html.LINK.parse(PlanAPI.getInstance().getPlayerInspectPageLink(operator), operator); + } + + inspectContainer.addValue(" " + getWithIcon("Operator", Icon.called("user").of(Color.YELLOW)), link); + inspectContainer.addValue(" " + getWithIcon("Date", Icon.called("calendar").of(Color.YELLOW).of(Family.REGULAR)), start); + inspectContainer.addValue(" " + getWithIcon("Ends", Icon.called("calendar-check").of(Color.YELLOW).of(Family.REGULAR)), end); + inspectContainer.addValue(" " + getWithIcon("Reason", Icon.called("comment").of(Color.YELLOW).of(Family.REGULAR)), reason); + inspectContainer.addValue(" " + getWithIcon("Jail", Icon.called("bars").of(Color.YELLOW).of(Family.SOLID)), inmate.getJailName()); + } + } + + private void addHomeData(User user, NucleusHomeService homeService, InspectContainer inspectContainer) { + int homeCount = homeService.getHomeCount(user); + int maxHomes = homeService.getMaximumHomes(user); + + inspectContainer.addValue(" " + getWithIcon("Homes", Icon.called("home").of(Color.GREEN).of(Family.SOLID)), homeCount + "/" + maxHomes); + + List homes = homeService.getHomes(user); + + if (!homes.isEmpty()) { + TableContainer homesTable = new TableContainer(getWithIcon("Home", Icon.called("home").of(Family.SOLID))); + homesTable.setColor("light-green"); + + for (Home home : homes) { + homesTable.addRow(home.getName()); + } + + inspectContainer.addTable("Homes", homesTable); + } + } + + private void addNoteData(User user, NucleusNoteService noteService, InspectContainer inspectContainer) { + List notes = noteService.getNotes(user); + + if (!notes.isEmpty()) { + TableContainer notesTable = new TableContainer( + getWithIcon("Noter", Icon.called("pen").of(Family.SOLID)), + getWithIcon("Note", Icon.called("sticky-note").of(Family.REGULAR)) + ); + + notesTable.setColor("light-blue"); + + for (Note note : notes) { + String noter = "Unknown"; + + User noterUser = note.getNoter().map(this::getUser).orElse(null); + if (noterUser != null) { + noter = noterUser.getName(); + } + + notesTable.addRow(noter, note.getNote()); + } + + inspectContainer.addTable("Notes", notesTable); + } + } + + private void addWarningData(User user, NucleusWarningService warningService, InspectContainer inspectContainer) { + List warnings = warningService.getWarnings(user); + inspectContainer.addValue(getWithIcon("Warning count", Icon.called("flag").of(Color.AMBER)), warnings.size()); + + if (!warnings.isEmpty()) { + TableContainer warningsTable = new TableContainer( + getWithIcon("Warner", Icon.called("exclamation").of(Family.SOLID)), + getWithIcon("Reason", Icon.called("sticky-note").of(Family.SOLID)) + ); + + warningsTable.setColor("amber"); + + for (Warning warning : warnings) { + String warner = "Unknown"; + + User warnerUser = warning.getWarner().map(this::getUser).orElse(null); + if (warnerUser != null) { + warner = warnerUser.getName(); + } + + warningsTable.addRow(warner, warning.getReason()); + } + + inspectContainer.addTable("Warnings", warningsTable); + } + } + + private void addInvulnerabilityData(User user, NucleusInvulnerabilityService invulnerabilityService, InspectContainer inspectContainer) { + boolean invulnerable = invulnerabilityService.isInvulnerable(user); + inspectContainer.addValue(getWithIcon("Invulnerable", Icon.called("crosshairs").of(Color.BLUE).of(Family.SOLID)), invulnerable ? "Yes" : "No"); + } + + private void addNicknameData(User user, NucleusNicknameService nicknameService, InspectContainer inspectContainer) { + Optional nickname = nicknameService.getNickname(user); + + if (nickname.isPresent()) { + String nicknameString = HtmlUtils.swapColorsToSpan(TextSerializers.FORMATTING_CODE.serialize(nickname.get())); + inspectContainer.addValue(" " + getWithIcon("Nickname", Icon.called("id-badge").of(Color.GREEN).of(Family.REGULAR)), nicknameString); + } + } + + /* + * Server Data + */ + private void addWarpData(NucleusWarpService warpService, AnalysisContainer analysisContainer) { + List warps = warpService.getAllWarps(); + analysisContainer.addValue(getWithIcon("Warp count", Icon.called("map-marker-alt").of(Color.BLUE)), warps.size()); + + if (!warps.isEmpty()) { + TableContainer warpsTable = new TableContainer( + getWithIcon("Name", Icon.called("map-marker-alt").of(Family.SOLID)), + getWithIcon("Description", Icon.called("sticky-note").of(Family.REGULAR)), + getWithIcon("Category", Icon.called("list").of(Family.SOLID)) + ); + + for (Warp warp : warps) { + String description = warp.getDescription().map(desc -> HtmlUtils.swapColorsToSpan(TextSerializers.FORMATTING_CODE.serialize(desc))).orElse("None"); + String category = warp.getCategory().orElse("None"); + + warpsTable.addRow(warp.getName(), description, category); + } + + analysisContainer.addTable("Warps", warpsTable); + } + } + + private void addJailData(NucleusJailService jailService, AnalysisContainer analysisContainer) { + Map jails = jailService.getJails(); + analysisContainer.addValue(getWithIcon("Jail count", Icon.called("bars").of(Family.SOLID).of(Color.TEAL)), jails.size()); + + if (!jails.isEmpty()) { + TableContainer jailsTable = new TableContainer(getWithIcon("Jail", Icon.called("bars").of(Family.SOLID))); + + for (String jail : jails.keySet()) { + jailsTable.addRow(jail); + } + + analysisContainer.addTable("Jails", jailsTable); + } + } + + private void addKitData(NucleusKitService kitService, AnalysisContainer analysisContainer) { + Set kits = kitService.getKitNames(); + analysisContainer.addValue(getWithIcon("Kit count", Icon.called("box").of(Family.SOLID)), kits.size()); + + if (!kits.isEmpty()) { + TableContainer kitsTable = new TableContainer(getWithIcon("Kit", Icon.called("box").of(Family.SOLID))); + + for (String kit : kits) { + kitsTable.addRow(kit); + } + + analysisContainer.addTable("Kits", kitsTable); + } + } +} diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/nucleus/NucleusHook.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/nucleus/NucleusHook.java new file mode 100644 index 000000000..d8a9afc36 --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/nucleus/NucleusHook.java @@ -0,0 +1,26 @@ +/* + * Licence is provided in the jar as license.yml also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml + */ +package com.djrapitops.pluginbridge.plan.nucleus; + +import com.djrapitops.plan.data.plugin.HookHandler; +import com.djrapitops.pluginbridge.plan.Hook; + +/** + * Hook for AdvancedBan plugin. + * + * @author Vankka + */ +public class NucleusHook extends Hook { + public NucleusHook(HookHandler hookHandler) { + super("io.github.nucleuspowered.nucleus.NucleusPlugin", hookHandler); + } + + @Override + public void hook() throws NoClassDefFoundError { + if (enabled) { + addPluginDataSource(new NucleusData()); + } + } +} From 67712f9b26d2997699cfdb4507778ad5243ec6a3 Mon Sep 17 00:00:00 2001 From: Yannick Schinko Date: Thu, 6 Sep 2018 22:19:43 +0200 Subject: [PATCH 48/76] Implemented support for sponge economy (#721) * Implement basic economy support for Sponge * Implemented global server stats --- .../djrapitops/pluginbridge/plan/Bridge.java | 2 + .../plan/sponge/SpongeEconomyData.java | 97 +++++++++++++++++++ .../plan/sponge/SpongeEconomyHook.java | 33 +++++++ 3 files changed, 132 insertions(+) create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/sponge/SpongeEconomyData.java create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/sponge/SpongeEconomyHook.java diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java index cdc1e6ce9..2adf0dfbd 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java @@ -22,6 +22,7 @@ import com.djrapitops.pluginbridge.plan.mcmmo.McmmoHook; import com.djrapitops.pluginbridge.plan.nucleus.NucleusHook; import com.djrapitops.pluginbridge.plan.protocolsupport.ProtocolSupportHook; import com.djrapitops.pluginbridge.plan.redprotect.RedProtectHook; +import com.djrapitops.pluginbridge.plan.sponge.SpongeEconomyHook; import com.djrapitops.pluginbridge.plan.superbvote.SuperbVoteHook; import com.djrapitops.pluginbridge.plan.towny.TownyHook; import com.djrapitops.pluginbridge.plan.vault.VaultHook; @@ -74,6 +75,7 @@ public class Bridge { private static Hook[] getSpongeHooks(HookHandler h) { return new Hook[]{ new BuyCraftHook(h), + new SpongeEconomyHook(h), new NucleusHook(h) }; } diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/sponge/SpongeEconomyData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/sponge/SpongeEconomyData.java new file mode 100644 index 000000000..efe27b3a8 --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/sponge/SpongeEconomyData.java @@ -0,0 +1,97 @@ +package com.djrapitops.pluginbridge.plan.sponge; + +import com.djrapitops.plan.data.element.AnalysisContainer; +import com.djrapitops.plan.data.element.InspectContainer; +import com.djrapitops.plan.data.plugin.ContainerSize; +import com.djrapitops.plan.data.plugin.PluginData; +import com.djrapitops.plan.data.store.keys.AnalysisKeys; +import com.djrapitops.plan.data.store.keys.PlayerKeys; +import com.djrapitops.plan.data.store.mutators.PlayersMutator; +import com.djrapitops.plan.system.cache.DataCache; +import com.djrapitops.plan.utilities.html.icon.Color; +import com.djrapitops.plan.utilities.html.icon.Icon; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.EconomyService; +import org.spongepowered.api.service.economy.account.UniqueAccount; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * PluginData for Sponge. + * + * @author BrainStone + */ +public class SpongeEconomyData extends PluginData { + private static final Color color = Color.AMBER; + private static final String nameMoneyIcon = "money-bill-wave"; + private static final Icon moneyIcon = Icon.called(nameMoneyIcon).build(); + private static final Icon moneyIconColored = Icon.called(nameMoneyIcon).of(color).build(); + + private final EconomyService economyService; + + public SpongeEconomyData(EconomyService economyService) { + super(ContainerSize.THIRD, "Sponge Economy"); + + this.economyService = economyService; + + setPluginIcon(moneyIconColored); + } + + @Override + public InspectContainer getPlayerData(UUID uuid, InspectContainer inspectContainer) { + String name = DataCache.getInstance().getName(uuid); + + if (name == null) { + return inspectContainer; + } + + Optional uOpt = economyService.getOrCreateAccount(uuid); + + if (!uOpt.isPresent()) { + return inspectContainer; + } + + UniqueAccount acc = uOpt.get(); + + for(Currency currency : economyService.getCurrencies()) { + BigDecimal balance = acc.getBalance(currency); + inspectContainer.addValue(getWithIcon(currency.getName(), moneyIconColored), currency.format(balance).toPlain()); + } + + return inspectContainer; + } + + @Override + public AnalysisContainer getServerData(Collection uuids, AnalysisContainer analysisContainer) { + List players = uuids.stream().map(economyService::getOrCreateAccount) + .filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); + + for(Currency currency : economyService.getCurrencies()) { + addCurrencyToContainer(currency, players, analysisContainer); + } + + return analysisContainer; + } + + private void addCurrencyToContainer(Currency currency, List players, AnalysisContainer analysisContainer) { + BigDecimal totalBalance = BigDecimal.ZERO; + Map playerBalances = new HashMap<>(); + + for (UniqueAccount player : players) { + BigDecimal balance = player.getBalance(currency); + + totalBalance = totalBalance.add(balance); + playerBalances.put(player.getUniqueId(), currency.format(balance).toPlain()); + } + + analysisContainer.addValue(getWithIcon("Total Server Balance " + currency.getName(), moneyIconColored), currency.format(totalBalance).toPlain()); + analysisContainer.addPlayerTableValues(getWithIcon("Balance " + currency.getName(), moneyIcon), playerBalances); + } +} diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/sponge/SpongeEconomyHook.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/sponge/SpongeEconomyHook.java new file mode 100644 index 000000000..c65c198c8 --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/sponge/SpongeEconomyHook.java @@ -0,0 +1,33 @@ +package com.djrapitops.pluginbridge.plan.sponge; + +import com.djrapitops.plan.data.plugin.HookHandler; +import com.djrapitops.pluginbridge.plan.Hook; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.service.economy.EconomyService; +import java.util.Optional; + +/** + * A Class responsible for hooking to Sponge and registering 1 data sources + * + * @author BrainStone + * @since 4.4.6 + */ +public class SpongeEconomyHook extends Hook { + public SpongeEconomyHook(HookHandler hookHandler) { + super("org.spongepowered.api.Sponge", hookHandler); + + try { + Optional serviceOpt = Sponge.getServiceManager().provide(EconomyService.class); + enabled = serviceOpt.isPresent(); + } catch(NoClassDefFoundError e) { + enabled = false; + } + } + + @Override + public void hook() { + if (enabled) { + addPluginDataSource(new SpongeEconomyData(Sponge.getServiceManager().provide(EconomyService.class).get())); + } + } +} From 4475c44c222d47e047c2c0d96b80c94172e0eb7e Mon Sep 17 00:00:00 2001 From: Brycey92 Date: Thu, 6 Sep 2018 16:34:33 -0400 Subject: [PATCH 49/76] Fixed plan.inspect and plan.qinspect permission nodes for Sponge --- .../com/djrapitops/plan/system/settings/Permissions.java | 8 ++++---- Plan/src/main/resources/plugin.yml | 8 ++++---- docs/main/java/com/djrapitops/plan/Permissions.html | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/settings/Permissions.java b/Plan/src/main/java/com/djrapitops/plan/system/settings/Permissions.java index 0a5377ccd..c61c54d5b 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/settings/Permissions.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/settings/Permissions.java @@ -10,8 +10,8 @@ public enum Permissions { HELP("plan.?"), - INSPECT("plan.inspect"), - QUICK_INSPECT("plan.qinspect"), + INSPECT("plan.inspect.base"), + QUICK_INSPECT("plan.qinspect.base"), INSPECT_OTHER("plan.inspect.other"), QUICK_INSPECT_OTHER("plan.qinspect.other"), @@ -36,7 +36,7 @@ public enum Permissions { /** * Returns the permission node in plugin.yml. * - * @return permission node eg. plan.inspect + * @return permission node eg. plan.inspect.base */ public String getPermission() { return permission; @@ -45,7 +45,7 @@ public enum Permissions { /** * Same as {@link #getPermission()}. * - * @return permission node eg. plan.inspect + * @return permission node eg. plan.inspect.base */ public String getPerm() { return getPermission(); diff --git a/Plan/src/main/resources/plugin.yml b/Plan/src/main/resources/plugin.yml index 55c0b3fe7..4b1b783fd 100644 --- a/Plan/src/main/resources/plugin.yml +++ b/Plan/src/main/resources/plugin.yml @@ -51,13 +51,13 @@ permissions: plan.?: description: Help command default: true - plan.inspect: + plan.inspect.base: description: Allows you to check your player data. default: true plan.inspect.other: description: Allows you to check other players' player data. default: op - plan.qinspect: + plan.qinspect.base: description: Allows you to check your player data. default: op plan.qinspect.other: @@ -93,8 +93,8 @@ permissions: plan.basic: children: plan.?: true - plan.inspect: true - plan.qinspect: true + plan.inspect.base: true + plan.qinspect.base: true plan.advanced: childer: plan.basic: true diff --git a/docs/main/java/com/djrapitops/plan/Permissions.html b/docs/main/java/com/djrapitops/plan/Permissions.html index 2ed30f1c4..549a9c7fc 100644 --- a/docs/main/java/com/djrapitops/plan/Permissions.html +++ b/docs/main/java/com/djrapitops/plan/Permissions.html @@ -430,7 +430,7 @@ not permitted.)
    Returns the permission node in plugin.yml.
    Returns:
    -
    permission node eg. plan.inspect
    +
    permission node eg. plan.inspect.base
    @@ -444,7 +444,7 @@ not permitted.)
    Returns:
    -
    permission node eg. plan.inspect
    +
    permission node eg. plan.inspect.base
    From 0fceaaf9e11736a600b930682b7fe9166dc729b6 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 8 Sep 2018 17:00:24 +0300 Subject: [PATCH 50/76] Updated PluginBridge jar --- PlanPluginBridge/PlanPluginBridge-4.4.0.jar | Bin 156141 -> 169161 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/PlanPluginBridge/PlanPluginBridge-4.4.0.jar b/PlanPluginBridge/PlanPluginBridge-4.4.0.jar index c39acfceac167d6b506b941bceb939e4417443ae..25b26975718cde71b772644ae2520761400953a7 100644 GIT binary patch delta 20838 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 delta 8188 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 From ac3e9a969cd7f9ae7c07deb2c61e01a19e519576 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 8 Sep 2018 17:11:29 +0300 Subject: [PATCH 51/76] Fixes Last Peak time not showing up #726 --- .../database/databases/sql/operation/SQLFetchOps.java | 4 ++-- .../system/database/databases/sql/tables/TPSTable.java | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java index 30e957451..51a3ebeaa 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java @@ -14,9 +14,9 @@ import com.djrapitops.plan.system.database.databases.operation.FetchOperations; import com.djrapitops.plan.system.database.databases.sql.SQLDB; import com.djrapitops.plan.system.info.server.Server; import com.djrapitops.plan.system.info.server.ServerInfo; -import com.djrapitops.plugin.api.TimeAmount; import java.util.*; +import java.util.concurrent.TimeUnit; public class SQLFetchOps extends SQLOps implements FetchOperations { @@ -72,7 +72,7 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { return null; }); container.putSupplier(ServerKeys.RECENT_PEAK_PLAYERS, () -> { - long twoDaysAgo = System.currentTimeMillis() - (TimeAmount.DAY.ms() * 2L); + long twoDaysAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2); Optional lastPeak = tpsTable.getPeakPlayerCount(serverUUID, twoDaysAgo); if (lastPeak.isPresent()) { TPS peak = lastPeak.get(); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/TPSTable.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/TPSTable.java index 0cc1773f1..661f6db8d 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/TPSTable.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/tables/TPSTable.java @@ -151,9 +151,12 @@ public class TPSTable extends Table { } public Optional getPeakPlayerCount(UUID serverUUID, long afterDate) { + String subStatement = "SELECT MAX(" + Col.PLAYERS_ONLINE + ") FROM " + tableName + + " WHERE " + Col.SERVER_ID + "=" + serverTable.statementSelectServerID + + " AND " + Col.DATE + ">= ?"; String sql = Select.all(tableName) .where(Col.SERVER_ID + "=" + serverTable.statementSelectServerID) - .and(Col.PLAYERS_ONLINE + "= (SELECT MAX(" + Col.PLAYERS_ONLINE + ") FROM " + tableName + ")") + .and(Col.PLAYERS_ONLINE + "= (" + subStatement + ")") .and(Col.DATE + ">= ?") .toString(); @@ -161,13 +164,14 @@ public class TPSTable extends Table { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, serverUUID.toString()); - statement.setLong(2, afterDate); + statement.setString(2, serverUUID.toString()); + statement.setLong(3, afterDate); + statement.setLong(4, afterDate); } @Override public Optional processResults(ResultSet set) throws SQLException { if (set.next()) { - TPS tps = TPSBuilder.get() .date(set.getLong(Col.DATE.get())) .tps(set.getDouble(Col.TPS.get())) From 2c5a97d0b1b7c2ccb95e89551b0b57e22c2f82a5 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 8 Sep 2018 17:15:50 +0300 Subject: [PATCH 52/76] Fixed MySQL with multiple plan databases returns wrong table count #724 --- .../plan/system/database/databases/sql/patches/Patch.java | 6 +++++- .../databases/sql/patches/VersionTableRemovalPatch.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java index e832d00f3..39ed66ed7 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java @@ -4,6 +4,7 @@ import com.djrapitops.plan.system.database.databases.sql.SQLDB; import com.djrapitops.plan.system.database.databases.sql.processing.QueryAllStatement; import com.djrapitops.plan.system.database.databases.sql.processing.QueryStatement; import com.djrapitops.plan.system.database.databases.sql.statements.TableSqlParser; +import com.djrapitops.plan.system.settings.Settings; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -29,13 +30,16 @@ public abstract class Patch { public boolean hasTable(String tableName) { String sql = usingMySQL ? - "SELECT * FROM information_schema.TABLES WHERE table_name=? LIMIT 1" : + "SELECT * FROM information_schema.TABLES WHERE table_name=? AND TABLE_SCHEMA=? LIMIT 1" : "SELECT tbl_name FROM sqlite_master WHERE tbl_name=?"; return query(new QueryStatement(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, tableName); + if (usingMySQL) { + statement.setString(2, Settings.DB_DATABASE.toString()); + } } @Override diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/VersionTableRemovalPatch.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/VersionTableRemovalPatch.java index c205cbded..b4252bfa0 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/VersionTableRemovalPatch.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/VersionTableRemovalPatch.java @@ -10,7 +10,7 @@ public class VersionTableRemovalPatch extends Patch { @Override public boolean hasBeenApplied() { - return hasTable("plan_version"); + return !hasTable("plan_version"); } @Override From 795479658226959ed900a31d1a9b6122e19056ac Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 8 Sep 2018 17:36:52 +0300 Subject: [PATCH 53/76] Attempt to reduce memory usage with selective caching in DataContainer Reduced use of CachingSupplier in every DataContainer to reduce the amount of String variables ending up in heap for a longer period of time. Impacts #685 --- .../store/containers/AnalysisContainer.java | 62 +++++++++---------- .../data/store/containers/DataContainer.java | 7 +++ .../store/containers/NetworkContainer.java | 22 +++---- .../databases/sql/operation/SQLFetchOps.java | 54 ++++++++-------- 4 files changed, 76 insertions(+), 69 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java b/Plan/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java index d11aeb46c..d871e35c0 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/store/containers/AnalysisContainer.java @@ -62,9 +62,9 @@ public class AnalysisContainer extends DataContainer { } private void addAnalysisSuppliers() { - putSupplier(AnalysisKeys.SESSIONS_MUTATOR, () -> SessionsMutator.forContainer(serverContainer)); - putSupplier(AnalysisKeys.TPS_MUTATOR, () -> TPSMutator.forContainer(serverContainer)); - putSupplier(AnalysisKeys.PLAYERS_MUTATOR, () -> PlayersMutator.forContainer(serverContainer)); + putCachingSupplier(AnalysisKeys.SESSIONS_MUTATOR, () -> SessionsMutator.forContainer(serverContainer)); + putCachingSupplier(AnalysisKeys.TPS_MUTATOR, () -> TPSMutator.forContainer(serverContainer)); + putCachingSupplier(AnalysisKeys.PLAYERS_MUTATOR, () -> PlayersMutator.forContainer(serverContainer)); addConstants(); addPlayerSuppliers(); @@ -101,7 +101,7 @@ public class AnalysisContainer extends DataContainer { } private void addServerProperties() { - putSupplier(AnalysisKeys.SERVER_NAME, () -> + putCachingSupplier(AnalysisKeys.SERVER_NAME, () -> getUnsafe(serverNames).getOrDefault(serverContainer.getUnsafe(ServerKeys.SERVER_UUID), "Plan") ); @@ -126,7 +126,7 @@ public class AnalysisContainer extends DataContainer { } private void addPlayerSuppliers() { - putSupplier(AnalysisKeys.PLAYER_NAMES, () -> serverContainer.getValue(ServerKeys.PLAYERS).orElse(new ArrayList<>()) + putCachingSupplier(AnalysisKeys.PLAYER_NAMES, () -> serverContainer.getValue(ServerKeys.PLAYERS).orElse(new ArrayList<>()) .stream().collect(Collectors.toMap( p -> p.getUnsafe(PlayerKeys.UUID), p -> p.getValue(PlayerKeys.NAME).orElse("?")) ) @@ -169,22 +169,22 @@ public class AnalysisContainer extends DataContainer { Key uniqueDay = new Key<>(PlayersMutator.class, "UNIQUE_DAY"); Key uniqueWeek = new Key<>(PlayersMutator.class, "UNIQUE_WEEK"); Key uniqueMonth = new Key<>(PlayersMutator.class, "UNIQUE_MONTH"); - putSupplier(newDay, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) + putCachingSupplier(newDay, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) .filterRegisteredBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_DAY_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); - putSupplier(newWeek, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) + putCachingSupplier(newWeek, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) .filterRegisteredBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_WEEK_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); - putSupplier(newMonth, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) + putCachingSupplier(newMonth, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) .filterRegisteredBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); - putSupplier(uniqueDay, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) + putCachingSupplier(uniqueDay, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) .filterPlayedBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_DAY_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); - putSupplier(uniqueWeek, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) + putCachingSupplier(uniqueWeek, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) .filterPlayedBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_WEEK_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); - putSupplier(uniqueMonth, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) + putCachingSupplier(uniqueMonth, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR) .filterPlayedBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); @@ -212,7 +212,7 @@ public class AnalysisContainer extends DataContainer { Key retentionDay = new Key<>(Integer.class, "RETENTION_DAY"); // compareAndFindThoseLikelyToBeRetained can throw exception. - putSupplier(retentionDay, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR).compareAndFindThoseLikelyToBeRetained( + putCachingSupplier(retentionDay, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR).compareAndFindThoseLikelyToBeRetained( getUnsafe(newDay).all(), getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO), getUnsafe(AnalysisKeys.PLAYERS_ONLINE_RESOLVER) ).count() @@ -224,13 +224,13 @@ public class AnalysisContainer extends DataContainer { return 0; } }); - putSupplier(AnalysisKeys.PLAYERS_RETAINED_WEEK, () -> + putCachingSupplier(AnalysisKeys.PLAYERS_RETAINED_WEEK, () -> getUnsafe(newWeek).filterRetained( getUnsafe(AnalysisKeys.ANALYSIS_TIME_WEEK_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME) ).count() ); - putSupplier(AnalysisKeys.PLAYERS_RETAINED_MONTH, () -> + putCachingSupplier(AnalysisKeys.PLAYERS_RETAINED_MONTH, () -> getUnsafe(newMonth).filterRetained( getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME) @@ -260,8 +260,8 @@ public class AnalysisContainer extends DataContainer { private void addSessionSuppliers() { Key sessionAccordion = new Key<>(SessionAccordion.class, "SESSION_ACCORDION"); - putSupplier(serverNames, () -> Database.getActive().fetch().getServerNames()); - putSupplier(sessionAccordion, () -> SessionAccordion.forServer( + putCachingSupplier(serverNames, () -> Database.getActive().fetch().getServerNames()); + putCachingSupplier(sessionAccordion, () -> SessionAccordion.forServer( getUnsafe(AnalysisKeys.SESSIONS_MUTATOR).all(), getSupplier(serverNames), () -> getUnsafe(AnalysisKeys.PLAYER_NAMES) @@ -301,13 +301,13 @@ public class AnalysisContainer extends DataContainer { Key sessionsDay = new Key<>(SessionsMutator.class, "SESSIONS_DAY"); Key sessionsWeek = new Key<>(SessionsMutator.class, "SESSIONS_WEEK"); Key sessionsMonth = new Key<>(SessionsMutator.class, "SESSIONS_MONTH"); - putSupplier(sessionsDay, () -> getUnsafe(AnalysisKeys.SESSIONS_MUTATOR) + putCachingSupplier(sessionsDay, () -> getUnsafe(AnalysisKeys.SESSIONS_MUTATOR) .filterSessionsBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_DAY_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); - putSupplier(sessionsWeek, () -> getUnsafe(AnalysisKeys.SESSIONS_MUTATOR) + putCachingSupplier(sessionsWeek, () -> getUnsafe(AnalysisKeys.SESSIONS_MUTATOR) .filterSessionsBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_WEEK_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); - putSupplier(sessionsMonth, () -> getUnsafe(AnalysisKeys.SESSIONS_MUTATOR) + putCachingSupplier(sessionsMonth, () -> getUnsafe(AnalysisKeys.SESSIONS_MUTATOR) .filterSessionsBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); @@ -320,7 +320,7 @@ public class AnalysisContainer extends DataContainer { private void addGraphSuppliers() { Key worldPie = new Key<>(WorldPie.class, "WORLD_PIE"); - putSupplier(worldPie, () -> new WorldPie(serverContainer.getValue(ServerKeys.WORLD_TIMES).orElse(new WorldTimes(new HashMap<>())))); + putCachingSupplier(worldPie, () -> new WorldPie(serverContainer.getValue(ServerKeys.WORLD_TIMES).orElse(new WorldTimes(new HashMap<>())))); putSupplier(AnalysisKeys.WORLD_PIE_SERIES, () -> getUnsafe(worldPie).toHighChartsSeries()); putSupplier(AnalysisKeys.GM_PIE_SERIES, () -> getUnsafe(worldPie).toHighChartsDrilldown()); putSupplier(AnalysisKeys.PLAYERS_ONLINE_SERIES, () -> @@ -335,12 +335,12 @@ public class AnalysisContainer extends DataContainer { new WorldMap(getUnsafe(AnalysisKeys.PLAYERS_MUTATOR).getGeolocations()).toHighChartsSeries() ); Key geolocationBarChart = new Key<>(GeolocationBarGraph.class, "GEOLOCATION_BAR_CHART"); - putSupplier(geolocationBarChart, () -> new GeolocationBarGraph(getUnsafe(AnalysisKeys.PLAYERS_MUTATOR))); + putCachingSupplier(geolocationBarChart, () -> new GeolocationBarGraph(getUnsafe(AnalysisKeys.PLAYERS_MUTATOR))); putSupplier(AnalysisKeys.COUNTRY_CATEGORIES, () -> getUnsafe(geolocationBarChart).toHighChartsCategories()); putSupplier(AnalysisKeys.COUNTRY_SERIES, () -> getUnsafe(geolocationBarChart).toHighChartsSeries()); Key pingGraph = new Key<>(PingGraph.class, "PING_GRAPH"); - putSupplier(pingGraph, () -> new PingGraph( + putCachingSupplier(pingGraph, () -> new PingGraph( PingMutator.forContainer(serverContainer).mutateToByMinutePings().all() )); putSupplier(AnalysisKeys.AVG_PING_SERIES, () -> getUnsafe(pingGraph).toAvgSeries()); @@ -353,9 +353,9 @@ public class AnalysisContainer extends DataContainer { getUnsafe(AnalysisKeys.NEW_PLAYERS_PER_DAY) ).toCalendarSeries()); - putSupplier(AnalysisKeys.ACTIVITY_DATA, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR).toActivityDataMap(getUnsafe(AnalysisKeys.ANALYSIS_TIME))); + putCachingSupplier(AnalysisKeys.ACTIVITY_DATA, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR).toActivityDataMap(getUnsafe(AnalysisKeys.ANALYSIS_TIME))); Key activityStackGraph = new Key<>(ActivityStackGraph.class, "ACTIVITY_STACK_GRAPH"); - putSupplier(activityStackGraph, () -> new ActivityStackGraph(getUnsafe(AnalysisKeys.ACTIVITY_DATA))); + putCachingSupplier(activityStackGraph, () -> new ActivityStackGraph(getUnsafe(AnalysisKeys.ACTIVITY_DATA))); putSupplier(AnalysisKeys.ACTIVITY_STACK_CATEGORIES, () -> getUnsafe(activityStackGraph).toHighChartsLabels()); putSupplier(AnalysisKeys.ACTIVITY_STACK_SERIES, () -> getUnsafe(activityStackGraph).toHighChartsSeries()); putSupplier(AnalysisKeys.ACTIVITY_PIE_SERIES, () -> @@ -376,17 +376,17 @@ public class AnalysisContainer extends DataContainer { Key tpsWeek = new Key<>(TPSMutator.class, "TPS_WEEK"); Key tpsDay = new Key<>(TPSMutator.class, "TPS_DAY"); - putSupplier(tpsMonth, () -> getUnsafe(AnalysisKeys.TPS_MUTATOR) + putCachingSupplier(tpsMonth, () -> getUnsafe(AnalysisKeys.TPS_MUTATOR) .filterDataBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); - putSupplier(tpsWeek, () -> getUnsafe(AnalysisKeys.TPS_MUTATOR) + putCachingSupplier(tpsWeek, () -> getUnsafe(AnalysisKeys.TPS_MUTATOR) .filterDataBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_WEEK_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); - putSupplier(tpsDay, () -> getUnsafe(AnalysisKeys.TPS_MUTATOR) + putCachingSupplier(tpsDay, () -> getUnsafe(AnalysisKeys.TPS_MUTATOR) .filterDataBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_DAY_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) ); - putSupplier(AnalysisKeys.PLAYERS_ONLINE_RESOLVER, () -> new PlayersOnlineResolver(getUnsafe(AnalysisKeys.TPS_MUTATOR))); + putCachingSupplier(AnalysisKeys.PLAYERS_ONLINE_RESOLVER, () -> new PlayersOnlineResolver(getUnsafe(AnalysisKeys.TPS_MUTATOR))); putSupplier(AnalysisKeys.TPS_SPIKE_MONTH, () -> getUnsafe(tpsMonth).lowTpsSpikeCount()); putSupplier(AnalysisKeys.AVG_TPS_MONTH, () -> getUnsafe(tpsMonth).averageTPS()); @@ -416,7 +416,7 @@ public class AnalysisContainer extends DataContainer { private void addServerHealth() { Key healthInformation = new Key<>(HealthInformation.class, "HEALTH_INFORMATION"); - putSupplier(healthInformation, () -> new HealthInformation(this)); + putCachingSupplier(healthInformation, () -> new HealthInformation(this)); putSupplier(AnalysisKeys.HEALTH_INDEX, () -> getUnsafe(healthInformation).getServerHealth()); putSupplier(AnalysisKeys.HEALTH_NOTES, () -> getUnsafe(healthInformation).toHtml()); } @@ -424,13 +424,13 @@ public class AnalysisContainer extends DataContainer { private void addPluginSuppliers() { // TODO Refactor into a system that supports running the analysis on Bungee Key navAndTabs = new Key<>(new Type() {}, "NAV_AND_TABS"); - putSupplier(navAndTabs, () -> + putCachingSupplier(navAndTabs, () -> AnalysisPluginsTabContentCreator.createContent( getUnsafe(AnalysisKeys.PLAYERS_MUTATOR), this ) ); - putSupplier(AnalysisKeys.BAN_DATA, () -> new ServerBanDataReader().readBanDataForContainer(this)); + putCachingSupplier(AnalysisKeys.BAN_DATA, () -> new ServerBanDataReader().readBanDataForContainer(this)); putSupplier(AnalysisKeys.PLUGINS_TAB_NAV, () -> getUnsafe(navAndTabs)[0]); putSupplier(AnalysisKeys.PLUGINS_TAB, () -> getUnsafe(navAndTabs)[1]); } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/store/containers/DataContainer.java b/Plan/src/main/java/com/djrapitops/plan/data/store/containers/DataContainer.java index 96d7ca388..b47d3ee03 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/store/containers/DataContainer.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/store/containers/DataContainer.java @@ -44,6 +44,13 @@ public class DataContainer { } public void putSupplier(Key key, Supplier supplier) { + if (supplier == null) { + return; + } + map.put(key, supplier); + } + + public void putCachingSupplier(Key key, Supplier supplier) { if (supplier == null) { return; } diff --git a/Plan/src/main/java/com/djrapitops/plan/data/store/containers/NetworkContainer.java b/Plan/src/main/java/com/djrapitops/plan/data/store/containers/NetworkContainer.java index dab7aae04..5bc3e758d 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/store/containers/NetworkContainer.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/store/containers/NetworkContainer.java @@ -46,7 +46,7 @@ public class NetworkContainer extends DataContainer { this.bungeeContainer = bungeeContainer; serverContainers = new HashMap<>(); - putSupplier(NetworkKeys.PLAYERS_MUTATOR, () -> PlayersMutator.forContainer(bungeeContainer)); + putCachingSupplier(NetworkKeys.PLAYERS_MUTATOR, () -> PlayersMutator.forContainer(bungeeContainer)); addConstants(); addPlayerInformation(); @@ -55,9 +55,9 @@ public class NetworkContainer extends DataContainer { private void addNetworkHealth() { Key healthInformation = new Key<>(NetworkHealthInformation.class, "HEALTH_INFORMATION"); - putSupplier(healthInformation, () -> new NetworkHealthInformation(this)); - putSupplier(NetworkKeys.HEALTH_INDEX, () -> getUnsafe(healthInformation).getServerHealth()); - putSupplier(NetworkKeys.HEALTH_NOTES, () -> getUnsafe(healthInformation).toHtml()); + putCachingSupplier(healthInformation, () -> new NetworkHealthInformation(this)); + putCachingSupplier(NetworkKeys.HEALTH_INDEX, () -> getUnsafe(healthInformation).getServerHealth()); + putCachingSupplier(NetworkKeys.HEALTH_NOTES, () -> getUnsafe(healthInformation).toHtml()); } public void putAnalysisContainer(AnalysisContainer analysisContainer) { @@ -90,7 +90,7 @@ public class NetworkContainer extends DataContainer { putRawData(NetworkKeys.VERSION, PlanPlugin.getInstance().getVersion()); putSupplier(NetworkKeys.TIME_ZONE, MiscUtils::getTimeZoneOffsetHours); - putSupplier(NetworkKeys.NETWORK_NAME, () -> + putCachingSupplier(NetworkKeys.NETWORK_NAME, () -> Check.isBungeeAvailable() ? Settings.BUNGEE_NETWORK_NAME.toString() : bungeeContainer.getValue(ServerKeys.NAME).orElse("Plan") @@ -146,22 +146,22 @@ public class NetworkContainer extends DataContainer { Key uniqueDay = new Key<>(PlayersMutator.class, "UNIQUE_DAY"); Key uniqueWeek = new Key<>(PlayersMutator.class, "UNIQUE_WEEK"); Key uniqueMonth = new Key<>(PlayersMutator.class, "UNIQUE_MONTH"); - putSupplier(newDay, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) + putCachingSupplier(newDay, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) .filterRegisteredBetween(getUnsafe(NetworkKeys.REFRESH_TIME_DAY_AGO), getUnsafe(NetworkKeys.REFRESH_TIME)) ); - putSupplier(newWeek, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) + putCachingSupplier(newWeek, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) .filterRegisteredBetween(getUnsafe(NetworkKeys.REFRESH_TIME_WEEK_AGO), getUnsafe(NetworkKeys.REFRESH_TIME)) ); - putSupplier(newMonth, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) + putCachingSupplier(newMonth, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) .filterRegisteredBetween(getUnsafe(NetworkKeys.REFRESH_TIME_MONTH_AGO), getUnsafe(NetworkKeys.REFRESH_TIME)) ); - putSupplier(uniqueDay, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) + putCachingSupplier(uniqueDay, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) .filterPlayedBetween(getUnsafe(NetworkKeys.REFRESH_TIME_DAY_AGO), getUnsafe(NetworkKeys.REFRESH_TIME)) ); - putSupplier(uniqueWeek, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) + putCachingSupplier(uniqueWeek, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) .filterPlayedBetween(getUnsafe(NetworkKeys.REFRESH_TIME_WEEK_AGO), getUnsafe(NetworkKeys.REFRESH_TIME)) ); - putSupplier(uniqueMonth, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) + putCachingSupplier(uniqueMonth, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR) .filterPlayedBetween(getUnsafe(NetworkKeys.REFRESH_TIME_MONTH_AGO), getUnsafe(NetworkKeys.REFRESH_TIME)) ); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java index 51a3ebeaa..7868a8873 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/operation/SQLFetchOps.java @@ -27,7 +27,7 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { @Override public NetworkContainer getNetworkContainer() { NetworkContainer networkContainer = new NetworkContainer(getBungeeServerContainer()); - networkContainer.putSupplier(NetworkKeys.BUKKIT_SERVERS, () -> getBukkitServers().values()); + networkContainer.putCachingSupplier(NetworkKeys.BUKKIT_SERVERS, () -> getBukkitServers().values()); return networkContainer; } @@ -38,8 +38,8 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { } ServerContainer container = getServerContainer(bungeeInfo.get().getUuid()); - container.putSupplier(ServerKeys.PLAYERS, this::getAllPlayerContainers); - container.putSupplier(ServerKeys.TPS, tpsTable::getNetworkOnlineData); + container.putCachingSupplier(ServerKeys.PLAYERS, this::getAllPlayerContainers); + container.putCachingSupplier(ServerKeys.TPS, tpsTable::getNetworkOnlineData); container.putSupplier(ServerKeys.WORLD_TIMES, null); // Additional Session information not supported container.putSupplier(ServerKeys.PLAYER_KILLS, null); container.putSupplier(ServerKeys.PLAYER_KILL_COUNT, null); @@ -58,12 +58,12 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { container.putRawData(ServerKeys.SERVER_UUID, serverUUID); container.putRawData(ServerKeys.NAME, serverInfo.get().getName()); - container.putSupplier(ServerKeys.PLAYERS, () -> getPlayerContainers(serverUUID)); + container.putCachingSupplier(ServerKeys.PLAYERS, () -> getPlayerContainers(serverUUID)); container.putSupplier(ServerKeys.PLAYER_COUNT, () -> container.getUnsafe(ServerKeys.PLAYERS).size()); - container.putSupplier(ServerKeys.TPS, () -> tpsTable.getTPSData(serverUUID)); - container.putSupplier(ServerKeys.PING, () -> PlayersMutator.forContainer(container).pings()); - container.putSupplier(ServerKeys.ALL_TIME_PEAK_PLAYERS, () -> { + container.putCachingSupplier(ServerKeys.TPS, () -> tpsTable.getTPSData(serverUUID)); + container.putCachingSupplier(ServerKeys.PING, () -> PlayersMutator.forContainer(container).pings()); + container.putCachingSupplier(ServerKeys.ALL_TIME_PEAK_PLAYERS, () -> { Optional allTimePeak = tpsTable.getAllTimePeak(serverUUID); if (allTimePeak.isPresent()) { TPS peak = allTimePeak.get(); @@ -71,7 +71,7 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { } return null; }); - container.putSupplier(ServerKeys.RECENT_PEAK_PLAYERS, () -> { + container.putCachingSupplier(ServerKeys.RECENT_PEAK_PLAYERS, () -> { long twoDaysAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2); Optional lastPeak = tpsTable.getPeakPlayerCount(serverUUID, twoDaysAgo); if (lastPeak.isPresent()) { @@ -81,22 +81,22 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { return null; }); - container.putSupplier(ServerKeys.COMMAND_USAGE, () -> commandUseTable.getCommandUse(serverUUID)); - container.putSupplier(ServerKeys.WORLD_TIMES, () -> worldTimesTable.getWorldTimesOfServer(serverUUID)); + container.putCachingSupplier(ServerKeys.COMMAND_USAGE, () -> commandUseTable.getCommandUse(serverUUID)); + container.putCachingSupplier(ServerKeys.WORLD_TIMES, () -> worldTimesTable.getWorldTimesOfServer(serverUUID)); // Calculating getters - container.putSupplier(ServerKeys.OPERATORS, () -> PlayersMutator.forContainer(container).operators()); - container.putSupplier(ServerKeys.SESSIONS, () -> { + container.putCachingSupplier(ServerKeys.OPERATORS, () -> PlayersMutator.forContainer(container).operators()); + container.putCachingSupplier(ServerKeys.SESSIONS, () -> { List sessions = PlayersMutator.forContainer(container).getSessions(); if (serverUUID.equals(ServerInfo.getServerUUID())) { sessions.addAll(SessionCache.getActiveSessions().values()); } return sessions; }); - container.putSupplier(ServerKeys.PLAYER_KILLS, () -> SessionsMutator.forContainer(container).toPlayerKillList()); - container.putSupplier(ServerKeys.PLAYER_KILL_COUNT, () -> container.getUnsafe(ServerKeys.PLAYER_KILLS).size()); - container.putSupplier(ServerKeys.MOB_KILL_COUNT, () -> SessionsMutator.forContainer(container).toMobKillCount()); - container.putSupplier(ServerKeys.DEATH_COUNT, () -> SessionsMutator.forContainer(container).toDeathCount()); + container.putCachingSupplier(ServerKeys.PLAYER_KILLS, () -> SessionsMutator.forContainer(container).toPlayerKillList()); + container.putCachingSupplier(ServerKeys.PLAYER_KILL_COUNT, () -> container.getUnsafe(ServerKeys.PLAYER_KILLS).size()); + container.putCachingSupplier(ServerKeys.MOB_KILL_COUNT, () -> SessionsMutator.forContainer(container).toMobKillCount()); + container.putCachingSupplier(ServerKeys.DEATH_COUNT, () -> SessionsMutator.forContainer(container).toDeathCount()); return container; } @@ -129,13 +129,13 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { container.putRawData(PlayerKeys.KICK_COUNT, timesKicked.get(uuid)); container.putRawData(PlayerKeys.GEO_INFO, geoInfo.get(uuid)); container.putRawData(PlayerKeys.PING, allPings.get(uuid)); - container.putSupplier(PlayerKeys.NICKNAMES, () -> nicknamesTable.getNicknameInformation(uuid)); + container.putCachingSupplier(PlayerKeys.NICKNAMES, () -> nicknamesTable.getNicknameInformation(uuid)); container.putRawData(PlayerKeys.PER_SERVER, perServerInfo.get(uuid)); container.putRawData(PlayerKeys.BANNED, userInfo.isBanned()); container.putRawData(PlayerKeys.OPERATOR, userInfo.isOperator()); - container.putSupplier(PlayerKeys.SESSIONS, () -> { + container.putCachingSupplier(PlayerKeys.SESSIONS, () -> { List playerSessions = sessions.getOrDefault(uuid, new ArrayList<>()); container.getValue(PlayerKeys.ACTIVE_SESSION).ifPresent(playerSessions::add); return playerSessions; @@ -143,7 +143,7 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { ); // Calculating getters - container.putSupplier(PlayerKeys.WORLD_TIMES, () -> { + container.putCachingSupplier(PlayerKeys.WORLD_TIMES, () -> { WorldTimes worldTimes = new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).flatMapWorldTimes(); container.getValue(PlayerKeys.ACTIVE_SESSION) .ifPresent(session -> worldTimes.add( @@ -187,10 +187,10 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { container.putRawData(PlayerKeys.KICK_COUNT, timesKicked.get(uuid)); container.putRawData(PlayerKeys.GEO_INFO, geoInfo.get(uuid)); container.putRawData(PlayerKeys.PING, allPings.get(uuid)); - container.putSupplier(PlayerKeys.NICKNAMES, () -> nicknamesTable.getNicknameInformation(uuid)); + container.putCachingSupplier(PlayerKeys.NICKNAMES, () -> nicknamesTable.getNicknameInformation(uuid)); container.putRawData(PlayerKeys.PER_SERVER, perServerInfo.get(uuid)); - container.putSupplier(PlayerKeys.SESSIONS, () -> { + container.putCachingSupplier(PlayerKeys.SESSIONS, () -> { List playerSessions = PerServerMutator.forContainer(container).flatMapSessions(); container.getValue(PlayerKeys.ACTIVE_SESSION).ifPresent(playerSessions::add); return playerSessions; @@ -285,21 +285,21 @@ public class SQLFetchOps extends SQLOps implements FetchOperations { container.putRawData(PlayerKeys.UUID, uuid); container.putAll(usersTable.getUserInformation(uuid)); - container.putSupplier(PlayerKeys.GEO_INFO, () -> geoInfoTable.getGeoInfo(uuid)); - container.putSupplier(PlayerKeys.PING, () -> pingTable.getPing(uuid)); - container.putSupplier(PlayerKeys.NICKNAMES, () -> nicknamesTable.getNicknameInformation(uuid)); - container.putSupplier(PlayerKeys.PER_SERVER, () -> getPerServerData(uuid)); + container.putCachingSupplier(PlayerKeys.GEO_INFO, () -> geoInfoTable.getGeoInfo(uuid)); + container.putCachingSupplier(PlayerKeys.PING, () -> pingTable.getPing(uuid)); + container.putCachingSupplier(PlayerKeys.NICKNAMES, () -> nicknamesTable.getNicknameInformation(uuid)); + container.putCachingSupplier(PlayerKeys.PER_SERVER, () -> getPerServerData(uuid)); container.putSupplier(PlayerKeys.BANNED, () -> new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).isBanned()); container.putSupplier(PlayerKeys.OPERATOR, () -> new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).isOperator()); - container.putSupplier(PlayerKeys.SESSIONS, () -> { + container.putCachingSupplier(PlayerKeys.SESSIONS, () -> { List sessions = new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).flatMapSessions(); container.getValue(PlayerKeys.ACTIVE_SESSION).ifPresent(sessions::add); return sessions; } ); - container.putSupplier(PlayerKeys.WORLD_TIMES, () -> + container.putCachingSupplier(PlayerKeys.WORLD_TIMES, () -> { WorldTimes worldTimes = new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).flatMapWorldTimes(); container.getValue(PlayerKeys.ACTIVE_SESSION).ifPresent(session -> worldTimes.add( From 3781115311e104946d1cfd3ff173ed97d4892a6d Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 8 Sep 2018 17:43:38 +0300 Subject: [PATCH 54/76] Reduce memory usage with changes to player page refreshing Old way: Pre-render & cache the html for a player page when the player joins, leaves or switches server New way: Render & cache the html when requested. Remove the rendered html from cache if the player joins, leaves or switches server This should prevent server from crashing if a bunch of bots join Impacts #685 --- .../info/PlayerPageUpdateProcessor.java | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/info/PlayerPageUpdateProcessor.java b/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/info/PlayerPageUpdateProcessor.java index e6cbe7cf7..8081c66bd 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/info/PlayerPageUpdateProcessor.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/info/PlayerPageUpdateProcessor.java @@ -1,11 +1,7 @@ package com.djrapitops.plan.system.processing.processors.info; -import com.djrapitops.plan.system.info.InfoSystem; -import com.djrapitops.plan.system.info.connection.WebExceptionLogger; -import com.djrapitops.plugin.api.Check; -import com.djrapitops.plugin.api.TimeAmount; -import com.djrapitops.plugin.task.AbsRunnable; -import com.djrapitops.plugin.task.RunnableFactory; +import com.djrapitops.plan.system.webserver.cache.PageId; +import com.djrapitops.plan.system.webserver.cache.ResponseCache; import java.util.UUID; @@ -19,20 +15,6 @@ public class PlayerPageUpdateProcessor implements Runnable { @Override public void run() { - if (!InfoSystem.getInstance().getConnectionSystem().isServerAvailable() || Check.isBungeeAvailable()) { - RunnableFactory.createNew("Generate Inspect page: " + uuid, new AbsRunnable() { - @Override - public void run() { - try { - - WebExceptionLogger.logIfOccurs(PlayerPageUpdateProcessor.class, - () -> InfoSystem.getInstance().generateAndCachePlayerPage(uuid) - ); - } finally { - cancel(); - } - } - }).runTaskLaterAsynchronously(TimeAmount.SECOND.ticks() * 5); - } + ResponseCache.clearResponse(PageId.PLAYER.of(uuid)); } } From 18be484c0d1ac7f5b1e9921801c7faa6be37953a Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 8 Sep 2018 17:57:24 +0300 Subject: [PATCH 55/76] Update versions.txt --- versions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/versions.txt b/versions.txt index 8f575e18b..f175248c3 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,4 @@ +DEV|4.4.5-b2|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.6-DEV2/Plan-4.4.5-b2-bukkit-bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.6-DEV2 REL|4.4.5|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.5-RELEASE/Plan-4.4.5.Bukkit.Bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.5-RELEASE REL|4.4.4|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.4-REL/Plan-4.4.4.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=240584 DEV|4.4.3-b3|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.4-DEV3/Plan-4.4.3-b3.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.4-DEV3 From 747453c1c4e6ae713c1f9ead3ff76c1ac9f2c9c6 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 8 Sep 2018 17:58:06 +0300 Subject: [PATCH 56/76] Update versions.txt --- versions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/versions.txt b/versions.txt index 8f575e18b..f175248c3 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,4 @@ +DEV|4.4.5-b2|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.6-DEV2/Plan-4.4.5-b2-bukkit-bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.6-DEV2 REL|4.4.5|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.5-RELEASE/Plan-4.4.5.Bukkit.Bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.5-RELEASE REL|4.4.4|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.4-REL/Plan-4.4.4.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=240584 DEV|4.4.3-b3|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.4-DEV3/Plan-4.4.3-b3.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.4-DEV3 From 26f64990d3f6b10d7f8231cd79916b2d02fbba69 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sun, 9 Sep 2018 13:50:38 +0300 Subject: [PATCH 57/76] Fixed new code smells in development branch - static final class variable names in PingCountTimerBukkit - plugin field hiding protected variable in SpongeTaskSystem - InspectPageResponse not overriding equals method --- .../plan/system/tasks/SpongeTaskSystem.java | 10 +++++----- .../tasks/server/PingCountTimerBukkit.java | 20 +++++++++---------- .../response/pages/InspectPageResponse.java | 15 ++++++++++++++ 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java index 4865ffcf9..229e1dc5f 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/SpongeTaskSystem.java @@ -10,19 +10,19 @@ import org.spongepowered.api.Sponge; import org.spongepowered.api.scheduler.Task; public class SpongeTaskSystem extends ServerTaskSystem { - - private final PlanSponge plugin; + + private final PlanSponge planSponge; public SpongeTaskSystem(PlanSponge plugin) { super(plugin, new SpongeTPSCountTimer(plugin)); - this.plugin = plugin; + this.planSponge = plugin; } @Override public void enable() { super.enable(); PingCountTimerSponge pingCountTimer = new PingCountTimerSponge(); - plugin.registerListener(pingCountTimer); + planSponge.registerListener(pingCountTimer); long startDelay = TimeAmount.SECOND.ticks() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); RunnableFactory.createNew("PingCountTimer", pingCountTimer) .runTaskTimer(startDelay, PingCountTimerSponge.PING_INTERVAL); @@ -31,7 +31,7 @@ public class SpongeTaskSystem extends ServerTaskSystem { @Override public void disable() { super.disable(); - for (Task task : Sponge.getScheduler().getScheduledTasks(plugin)) { + for (Task task : Sponge.getScheduler().getScheduledTasks(planSponge)) { task.cancel(); } } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBukkit.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBukkit.java index 97c818546..2da73a083 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBukkit.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerBukkit.java @@ -59,17 +59,17 @@ public class PingCountTimerBukkit extends AbsRunnable implements Listener { //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/PlayerConnection.java#L178 public static final int PING_INTERVAL = 2 * 20; - private static final boolean pingMethodAvailable; + private static final boolean PING_METHOD_AVAILABLE; - private static final MethodHandle pingField; - private static final MethodHandle getHandleMethod; + private static final MethodHandle PING_FIELD; + private static final MethodHandle GET_HANDLE_METHOD; static { - pingMethodAvailable = isPingMethodAvailable(); + PING_METHOD_AVAILABLE = isPingMethodAvailable(); MethodHandle localHandle = null; MethodHandle localPing = null; - if (!pingMethodAvailable) { + if (!PING_METHOD_AVAILABLE) { Class craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer"); Class entityPlayer = Reflection.getMinecraftClass("EntityPlayer"); @@ -84,8 +84,8 @@ public class PingCountTimerBukkit extends AbsRunnable implements Listener { } } - getHandleMethod = localHandle; - pingField = localPing; + GET_HANDLE_METHOD = localHandle; + PING_FIELD = localPing; } private final Map>> playerHistory = new HashMap<>(); @@ -133,7 +133,7 @@ public class PingCountTimerBukkit extends AbsRunnable implements Listener { } private int getPing(Player player) { - if (pingMethodAvailable) { + if (PING_METHOD_AVAILABLE) { return player.spigot().getPing(); } @@ -142,8 +142,8 @@ public class PingCountTimerBukkit extends AbsRunnable implements Listener { private int getReflectionPing(Player player) { try { - Object entityPlayer = getHandleMethod.invoke(player); - return (int) pingField.invoke(entityPlayer); + Object entityPlayer = GET_HANDLE_METHOD.invoke(player); + return (int) PING_FIELD.invoke(entityPlayer); } catch (Exception ex) { return -1; } catch (Throwable throwable) { diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/InspectPageResponse.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/InspectPageResponse.java index 926f474ee..d201f4657 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/InspectPageResponse.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/pages/InspectPageResponse.java @@ -9,6 +9,7 @@ import org.apache.commons.text.StringSubstitutor; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.UUID; /** @@ -48,4 +49,18 @@ public class InspectPageResponse extends PageResponse { refreshPage.replacePlaceholders(); return new InspectPageResponse(null, refreshPage.getContent()); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof InspectPageResponse)) return false; + if (!super.equals(o)) return false; + InspectPageResponse that = (InspectPageResponse) o; + return Objects.equals(uuid, that.uuid); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), uuid); + } } From d28e20bf9a5b1af9bde5f2d03da1a6b8405c9738 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sun, 9 Sep 2018 17:14:20 +0300 Subject: [PATCH 58/76] Implemented equals & hashCode for UserImportData --- .../processing/importing/UserImportData.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/processing/importing/UserImportData.java b/Plan/src/main/java/com/djrapitops/plan/system/processing/importing/UserImportData.java index b4ce14a3d..37cf6d2f2 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/processing/importing/UserImportData.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/processing/importing/UserImportData.java @@ -276,4 +276,28 @@ public class UserImportData { return new UserImportData(name, uuid, nicknames, registered, op, banned, timesKicked, ips, worldTimes, kills, mobKills, deaths); } } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UserImportData)) return false; + UserImportData that = (UserImportData) o; + return registered == that.registered && + op == that.op && + banned == that.banned && + timesKicked == that.timesKicked && + mobKills == that.mobKills && + deaths == that.deaths && + Objects.equals(name, that.name) && + Objects.equals(uuid, that.uuid) && + Objects.equals(nicknames, that.nicknames) && + Objects.equals(ips, that.ips) && + Objects.equals(worldTimes, that.worldTimes) && + Objects.equals(kills, that.kills); + } + + @Override + public int hashCode() { + return Objects.hash(name, uuid, nicknames, registered, op, banned, timesKicked, ips, worldTimes, kills, mobKills, deaths); + } } From f846bd5b0eda6ef907bc8a7f48fb32acb165c22b Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 15 Sep 2018 10:03:35 +0300 Subject: [PATCH 59/76] AnalysisContainer no longer held in memory by PluginData objects. Since PluginData objects are more persistent than AnalysisContainer, objects related to the each analysis can not be freed until the next analysis has been performed, because a reference was held in each PluginData object. Change: set the reference to point to null in a finally block after getServerData call. AnalysisContainer can now be freed by GC at any time Affected issues: #685 --- .../html/structure/AnalysisPluginsTabContentCreator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/html/structure/AnalysisPluginsTabContentCreator.java b/Plan/src/main/java/com/djrapitops/plan/utilities/html/structure/AnalysisPluginsTabContentCreator.java index c8094884a..50d6ee6cc 100644 --- a/Plan/src/main/java/com/djrapitops/plan/utilities/html/structure/AnalysisPluginsTabContentCreator.java +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/html/structure/AnalysisPluginsTabContentCreator.java @@ -118,6 +118,7 @@ public class AnalysisPluginsTabContentCreator { Log.toLog(AnalysisPluginsTabContentCreator.class, e); } finally { Benchmark.stop("Analysis", "Analysis: Source " + source.getSourcePlugin()); + source.setAnalysisData(null); } }); return containers; From 3ae0855ef5d1837fb0c47c2471ced766d35aa5c4 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 15 Sep 2018 10:38:05 +0300 Subject: [PATCH 60/76] Named Plan ExecutorService pools, Fixed WebServer thread leak on reload WebServer ThreadPoolExecutor was never shutdown, as it was assumed HTTPServer.shutdown() would perform that. In extreme cases 250 reloads could lead to a OutOfMemoryException due to Heap size allocation for threads not being possible. Change: Shut down ThreadPoolExecutor manually. --- .../plan/system/processing/Processing.java | 5 ++-- .../plan/system/webserver/WebServer.java | 27 +++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/processing/Processing.java b/Plan/src/main/java/com/djrapitops/plan/system/processing/Processing.java index 35a039693..ce9e9df9a 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/processing/Processing.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/processing/Processing.java @@ -9,6 +9,7 @@ import com.djrapitops.plan.system.locale.lang.PluginLang; import com.djrapitops.plugin.StaticHolder; import com.djrapitops.plugin.api.utility.log.Log; import com.djrapitops.plugin.utilities.Verify; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.List; import java.util.concurrent.*; @@ -23,8 +24,8 @@ public class Processing implements SubSystem { public Processing(Supplier locale) { this.locale = locale; - nonCriticalExecutor = Executors.newFixedThreadPool(6); - criticalExecutor = Executors.newFixedThreadPool(2); + nonCriticalExecutor = Executors.newFixedThreadPool(6, new ThreadFactoryBuilder().setNameFormat("Plan Non critical-pool-%d").build()); + criticalExecutor = Executors.newFixedThreadPool(2, new ThreadFactoryBuilder().setNameFormat("Plan Critical-pool-%d").build()); saveInstance(nonCriticalExecutor); saveInstance(criticalExecutor); saveInstance(this); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/WebServer.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/WebServer.java index c8d90b4e3..d2b4a4a87 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/WebServer.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/WebServer.java @@ -12,6 +12,7 @@ import com.djrapitops.plugin.StaticHolder; import com.djrapitops.plugin.api.Check; import com.djrapitops.plugin.api.utility.log.Log; import com.djrapitops.plugin.utilities.Verify; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsParameters; @@ -27,9 +28,7 @@ import java.nio.file.Paths; import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateException; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.function.Supplier; /** @@ -108,7 +107,11 @@ public class WebServer implements SubSystem { } server.createContext("/", requestHandler); - server.setExecutor(new ThreadPoolExecutor(4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100))); + ExecutorService executor = new ThreadPoolExecutor( + 4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), + new ThreadFactoryBuilder().setNameFormat("Plan WebServer Thread-%d").build() + ); + server.setExecutor(executor); server.start(); enabled = true; @@ -199,12 +202,26 @@ public class WebServer implements SubSystem { @Override public void disable() { if (server != null) { + shutdown(); Log.info(locale.get().getString(PluginLang.DISABLED_WEB_SERVER)); - server.stop(0); } enabled = false; } + private void shutdown() { + server.stop(0); + Executor executor = server.getExecutor(); + if (executor instanceof ExecutorService) { + ExecutorService service = (ExecutorService) executor; + service.shutdown(); + try { + service.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException timeoutExceededEx) { + service.shutdownNow(); + } + } + } + public String getProtocol() { return usingHttps ? "https" : "http"; } From 764a1c661cbaad5fac6d06862d36a0d4fdd1d913 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 15 Sep 2018 14:38:42 +0300 Subject: [PATCH 61/76] Fixed a possible buffer memory leak in GeolocationCache on first enable http://www.evanjones.ca/java-native-leak-bug.html "TL;DR: Always close GZIPInputStream and GZIPOutputStream since they use native memory via zlib." --- .../djrapitops/plan/system/cache/GeolocationCache.java | 9 +++++++-- .../plan/system/webserver/response/Response.java | 6 ++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/cache/GeolocationCache.java b/Plan/src/main/java/com/djrapitops/plan/system/cache/GeolocationCache.java index 3e7a752dc..0dbe32cb0 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/cache/GeolocationCache.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/cache/GeolocationCache.java @@ -16,6 +16,7 @@ import com.maxmind.geoip2.record.Country; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; @@ -147,8 +148,12 @@ public class GeolocationCache implements SubSystem { return; } URL downloadSite = new URL("http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz"); - try (ReadableByteChannel rbc = Channels.newChannel(new GZIPInputStream(downloadSite.openStream())); - FileOutputStream fos = new FileOutputStream(getInstance().geolocationDB.getAbsoluteFile())) { + try ( + InputStream in = downloadSite.openStream(); + GZIPInputStream gzipIn = new GZIPInputStream(in); + ReadableByteChannel rbc = Channels.newChannel(gzipIn); + FileOutputStream fos = new FileOutputStream(getInstance().geolocationDB.getAbsoluteFile()) + ) { fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); } } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/Response.java b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/Response.java index 0456b76ba..349a209c4 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/Response.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/webserver/response/Response.java @@ -92,8 +92,10 @@ public abstract class Response { ? getContent() : locale.replaceMatchingLanguage(getContent()); - try (GZIPOutputStream out = new GZIPOutputStream(exchange.getResponseBody()); - ByteArrayInputStream bis = new ByteArrayInputStream(sentContent.getBytes(StandardCharsets.UTF_8))) { + try ( + GZIPOutputStream out = new GZIPOutputStream(exchange.getResponseBody()); + ByteArrayInputStream bis = new ByteArrayInputStream(sentContent.getBytes(StandardCharsets.UTF_8)) + ) { byte[] buffer = new byte[2048]; int count; while ((count = bis.read(buffer)) != -1) { From ef9bf00ddf3c09fed16a70f1e6e0845e6c5f6e5f Mon Sep 17 00:00:00 2001 From: Sprungente Date: Sat, 15 Sep 2018 17:56:37 +0200 Subject: [PATCH 62/76] Improved DE Locale (#730) (Sprungente) --- Plan/src/main/resources/locale/locale_DE.txt | 74 ++++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Plan/src/main/resources/locale/locale_DE.txt b/Plan/src/main/resources/locale/locale_DE.txt index 5e0c30a3e..26a694b20 100644 --- a/Plan/src/main/resources/locale/locale_DE.txt +++ b/Plan/src/main/resources/locale/locale_DE.txt @@ -17,10 +17,10 @@ Cmd Header - Players || > §2Spieler Cmd Header - Search || > §2${0} Ergebnisse für §f${1}§2: Cmd Header - Servers || > §2Server Cmd Header - Web Users || > §2${0} Accounts -Cmd Info - Bungee Connection || §2Verbuunden mit Bungee: §f${0} +Cmd Info - Bungee Connection || §2Verbunden mit Bungee: §f${0} Cmd Info - Database || §2Genutzte Datenbank: §f${0} Cmd Info - Reload Complete || §aReload erfolgreich. -Cmd Info - Reload Failed || §cBeim Reload ist etwas schief gelaufen, ein Neustart wird empfohlen. +Cmd Info - Reload Failed || §cBeim Reload ist etwas schief gelaufen. Es wird empfohlen, den Server neuzustarten. Cmd Info - Update || §2Update verfügbar: §f${0} Cmd Info - Version || §2Version: §f${0} Cmd Notify - No WebUser || Möglicherweise hast du keinen Account. Erstelle einen mit /plan register @@ -29,58 +29,58 @@ Cmd Qinspect - Activity Index || §2Aktivitätsindex: §f${0 Cmd Qinspect - Deaths || §2Tode: §f${0} Cmd Qinspect - Geolocation || §2Eingeloggt aus: §f${0} Cmd Qinspect - Last Seen || §2Zuletzt gesehen: §f${0} -Cmd Qinspect - Longest Session || §2Längste Sitzung: §f${0} +Cmd Qinspect - Longest Session || §2Längste Session: §f${0} Cmd Qinspect - Mob Kills || §2Getötete Mobs: §f${0} Cmd Qinspect - Player Kills || §2Getötete Spieler: §f${0} Cmd Qinspect - Playtime || §Spielzeit: §f${0} Cmd Qinspect - Registered || §2Registrierung: §f${0} Cmd Qinspect - Times Kicked || §2Kicks: §f${0} -Cmd Setup - Allowed || §aSet-up ist nun erlaubt. -Cmd Setup - Bad Request || §eVerbindung hergestellt, der empfangende Server ist kein Bungee-Server. Nutze stattdessen die Bungee-Adresse. -Cmd Setup - Disallowed || §cSet-up ist nun nicht mehr erlaubt. -Cmd Setup - Forbidden || §eVerbindung hergestellt, aber der Bungee-Server hat den Set-up-Modus ausgeschaltet - nutze '/planbungee setup' um ihn zu aktivieren. -Cmd Setup - Gateway Error || §eVerbindung hergestellt, aber der Bungee-Server konnte sich nicht mit diesem Server verbinden (Wurde der aktuelle Server neugestartet?). Nutze /plan m con & /planbungee con zum debuggen. +Cmd Setup - Allowed || §aSetupmodus wurde aktiviert. +Cmd Setup - Bad Request || §eVerbindung hergestellt. Der empfangende Server ist kein Bungeecordserver. Nutze stattdessen die Bungeecord-Adresse. +Cmd Setup - Disallowed || §cSet-up wurde deaktiviert. +Cmd Setup - Forbidden || §eVerbindung hergestellt aber der Bungeecordserver hat den Setupmodus nicht aktiviert. Nutze '/planbungee setup' um ihn zu aktivieren. +Cmd Setup - Gateway Error || §eVerbindung hergestellt, aber der Bungeecordserver konnte sich nicht mit diesem Server verbinden (Wurde der aktuelle Server neugestartet?). Nutze /plan m con & /planbungee con zum debuggen. Cmd Setup - Generic Fail || §eVerbindung fehlgeschlagen: ${0} -Cmd Setup - Internal Error || §eVerbindung hergestellt. ${0}, eventuelle Fehler kannst du dem Error-Log auf der Debugseite des empfangenden Servers einsehen. +Cmd Setup - Internal Error || §eVerbindung hergestellt. ${0}, eventuelle Fehler kannst du dem Error-Log auf der Debugseite des empfangenden Servers entnehmen. Cmd Setup - Success || §aVerbindung erfolgreich, Plan startet eventuell in ein paar Sekunden neu. Cmd Setup - Unauthorized || §eVerbindung erfolgreich, aber der empfangende Server hat diesen Server nicht autorisiert. Auf dem Plan-Discord bekommst du Hilfe. Cmd Setup - Url mistake || §cNutze die gesamte Adresse (Beginnend mit http:// oder https://) - Diese kannst du dem Bungee enable log entnehmen. Cmd Setup - WebServer not Enabled || §cWebServer ist auf diesem Server deaktiviert! Dies sollte beim Start aktiviert werden! -Cmd SUCCESS - Feature disabled || §a'${0}' wurde bist zum nächsten Reload des Plugins deaktiviert. +Cmd SUCCESS - Feature disabled || §a'${0}' wurde bis zum nächsten Reload des Plugins deaktiviert. Cmd SUCCESS - WebUser register || §aNeuer Account (${0}) erfolgreich hinzugefügt! -Cmd Update - Cancel Success || §aAbbruch erfolgreich. +Cmd Update - Cancel Success || §aErfolgreich abgebrochen. Cmd Update - Cancelled || §cUpdate abgebrochen. Cmd Update - Change log || Change Log v${0}: -Cmd Update - Fail Cacnel || §cAuf einem Server war das Update nicht erfolgreich, breche auf allen anderen Servern die Updates ab... +Cmd Update - Fail Cacnel || §cAuf einem Server war das Update nicht erfolgreich. Das Update wird auf allen Servern abgebrochen Cmd Update - Fail Force Notify || §e${0} wurde nicht geupdated, -force wurde angegeben, fahre mit Updates fort. -Cmd Update - Fail Not Online || §cNicht alle Server sind online oder erreichbar, erreichbare Server kannst du mit /plan update -u -force updaten -Cmd Update - Notify Cancel || §aDu kannst das Update auf Servern, die noch nicht neugestartet wurden verwerfen mit: /plan update cancel. -Cmd Update - Online Check || Überprüfe, ob alle Server online sind. +Cmd Update - Fail Not Online || §cNicht alle Server sind online oder erreichbar. Erreichbare Server kannst du mit /plan update -u -force updaten +Cmd Update - Notify Cancel || §aDu kannst das Update auf Servern, die noch nicht neugestartet wurden, verwerfen mit: /plan update cancel. +Cmd Update - Online Check || Überprüfe ob alle Server online sind. Cmd Update - Scheduled || §aUpdate für ${0} geplant. Cmd Update - Url mismatch || §cDie Download-URL beginnt nicht mit ${0} und ist evtl. nicht vertrauenswürdig. Du kannst diese Version manuell hier downloaden (direkter Download): Cmd Web - Permission Levels || >\§70: Zugriff auf alle Seiten\§71: Zugriff auf '/players' Und alle Spielerseiten\§72: Zugriff auf alle Spielerseiten mit dem gleichen Username wie der Web-Account\§73+: Keine Berechtigung Command Help - /plan analyze || Server-Übersicht Command Help - /plan dev || Entwicklungsmodus-Befehl -Command Help - /plan help || Zeigt eine Befehlsliste -Command Help - /plan info || Zeigt die Version von Plan -Command Help - /plan inspect || Zeigt eine Spielerseite +Command Help - /plan help || Zeigt eine Befehlsliste an +Command Help - /plan info || Zeigt die Version von Plan an +Command Help - /plan inspect || Zeigt eine Spielerseite an Command Help - /plan manage || Verwaltet die Plan-Datenbank Command Help - /plan manage backup || Erstellt ein Backup der Datenbank -Command Help - /plan manage clear || Leer die Datenbank +Command Help - /plan manage clear || Datenbank leeren Command Help - /plan manage con || Debug Server-Bungee Verbindungen Command Help - /plan manage disable || Schalte eine Funktion temporär aus Command Help - /plan manage hotswap || Ändere die Datenbank schnell -Command Help - /plan manage import || Importiere Daten -Command Help - /plan manage move || Bewege die Daten zwischen Datenbanken +Command Help - /plan manage import || Daten importieren +Command Help - /plan manage move || Bewege die Daten zwischen den Datenbanken Command Help - /plan manage remove || Entferne die Daten eines Spielers Command Help - /plan manage restore || Spiele ein Backup ein Command Help - /plan manage setup || Stelle die Server-Bungee-Verbindung her Command Help - /plan network || Netzwerk-Seite Command Help - /plan players || Spieler-Seite -Command Help - /plan qinspect || Zeige Spielerinfo im Spiel +Command Help - /plan qinspect || Zeigt die Spielerinfo im Spiel Command Help - /plan register || Registriere einen Account -Command Help - /plan reload || Starte Plan neu -Command Help - /plan search || Suche nach einem Spieler +Command Help - /plan reload || Plan neuladen +Command Help - /plan search || Nach einem Spieler suchen Command Help - /plan servers || Liste die Server in der Datenbank auf Command Help - /plan update || Zeige das Änderungsprotokoll oder update den Server Command Help - /plan web check || Infos über einen Account @@ -92,11 +92,11 @@ Command Help - /planbungee con || Debug Bungee-Server Verbindun Command Help - /planbungee disable || Deaktiviert das Plugin temporär Command Help - /planbungee setup || Schaltet Setup-Modus an oder aus Database - Apply Patch || Wende Patch an: ${0}.. -Database - Patches Applied || Alle Datenbankpatches wurden erfolgreich angewandt. -Database - Patches Applied Already || Alle Datenbankpatches wurden bereits angewandt. +Database - Patches Applied || Alle Datenbankpatches wurden erfolgreich angewendet. +Database - Patches Applied Already || Alle Datenbankpatches wurden bereits angewendet. Database MySQL - Launch Options Error || Startoptionen sind falsch, nutze Voreinstellungen (${0}) Database Notify - Clean || Daten von ${0} Spielern gelöscht. -Database Notify - SQLite No WAL || SQLite WAL auf dieser Serverversion nicht unterstützt, nutze Voreinstellungen. Dies beeinträchtigt möglicherweise die Serverperformance. +Database Notify - SQLite No WAL || SQLite WAL wird auf dieser Serverversion nicht unterstützt, nutze Voreinstellungen. Dies beeinträchtigt möglicherweise die Serverperformance. Disable || Player Analytics ausgeschaltet. Disable - Processing || Verarbeite kritische unverarbeitete Aufgaben. (${0}) Disable - Processing Complete || Verarbeitung komplett. @@ -104,15 +104,15 @@ Disable - WebServer || Webserver deaktiviert. Enable || Player Analytics angeschaltet. Enable - Database || ${0}-dDatenbankverbindung hergestellt. Enable - Notify Address Confirmation || Versichere dich, dass die Adresse auf DIESEN Server verweist: ${0} -Enable - Notify Empty IP || IP in der server.properties ist leer & AlternativeIP ist nicht in Verwendung. Falsche Links werden verwendet! +Enable - Notify Empty IP || IP in der server.properties ist leer & AlternativeIP ist nicht in Verwendung. Es werden falsche Links verwendet! Enable - Notify Geolocations disabled || Geolocation wird nicht aufgezeichnet (Data.Geolocations: false) -Enable - Notify Geolocations Internet Required || Plan braucht Internetzugang um die GeoLite2 Geolocation Datenbank runterzuladen. -Enable - Notify Webserver disabled || WebServer wurde nicht initialisiert. (WebServer.DisableWebServer: true) +Enable - Notify Geolocations Internet Required || Plan braucht einen Internetzugang um die GeoLite2 Geolocation Datenbank runterzuladen. +Enable - Notify Webserver disabled || WebServer wurde nicht geladen. (WebServer.DisableWebServer: true) Enable - WebServer || Webserver läuft auf PORT ${0} (${1}) Enable FAIL - Database || ${0}-Datenbankverbindung fehlgeschlagen: ${1} -Enable FAIL - Database Patch || Datenbank-Patch ist fehlgeschlagen, Plugin wurde deaktiviert. Bitte melde dies. +Enable FAIL - Database Patch || Datenbank-Patch ist fehlgeschlagen. Plugin wurde deaktiviert. Wir bitten dich, uns diesen Vorfall mitzuteilen. Enable FAIL - GeoDB Write || Etwas ist beim Speichern der GeoLite2 Geolocation Datenbank fehlgeschlagen -Enable FAIL - WebServer (Bungee) || Webserver ist nicht initialisiert! +Enable FAIL - WebServer (Bungee) || Webserver ist nicht geladen Enable FAIL - Wrong Database Type || ${0} ist keine gültige Datenbank HTML - ACTIVITY_INDEX || Aktivitätsindex HTML - ALL || Gesamt @@ -166,7 +166,7 @@ HTML - NAV_COMMAND_USAGE || Befehlsverwendung HTML - NAV_GEOLOCATIONS || Geolocations HTML - NAV_INFORMATION || Information HTML - NAV_NETWORK_PLAYERS || Netzwerk Spieler -HTML - NAV_ONLINE_ACTIVITY || Online Aktivität +HTML - NAV_ONLINE_ACTIVITY || Onlineaktivität HTML - NAV_OVERVIEW || Übersicht HTML - NAV_PERFORMANCE || Performance HTML - NAV_PLAYERS || Spieler @@ -205,7 +205,7 @@ HTML - PUNCHCARD || LOCHKARTE HTML - RECENT_LOGINS || Letzte LOGINS HTML - REGISTERED || REGISTRIERT HTML - REGISTERED_TEXT || Registriert -HTML - REGULAR || REGELMÄSSIG +HTML - REGULAR || REGELMÄSSIGE HTML - SEEN_NICKNAMES || Registrierte Nicknames HTML - SERVER || Server HTML - SERVER_ANALYSIS || Server Analyse @@ -221,8 +221,8 @@ HTML - SESSIONS || Sessions HTML - TIME || Zeit HTML - TIMES_KICKED || Mal gekickt HTML - TIMES_USED || Mal benutzt -HTML - TOTAL_ACTIVE_TEXT || Gesamt Aktiv -HTML - TOTAL_AFK || Gesamt AFK +HTML - TOTAL_ACTIVE_TEXT || Gesamte Aktive Spielzeit +HTML - TOTAL_AFK || Gesamte AFK-Zeit HTML - TOTAL_PLAYERS || Gesamte Spieler HTML - TOTAL_PLAYTIME || Gesamte Spielzeit HTML - UNIQUE || EINZIGARTIG @@ -252,7 +252,7 @@ HTML ERRORS - NOT_FOUND_404 || Nicht gefunden. HTML ERRORS - NOT_PLAYED_404 || Der Spieler war nie auf dem Server. HTML ERRORS - PAGE_NOT_FOUND_404 || Diese Seite existiert nicht. HTML ERRORS - UNAUTHORIZED_401 || Unautorisiert -HTML ERRORS - UNKNOWN_PAGE_404 || Stelle sicher, dass du einen Link benutzt, der von einem Command generiert wurde. Beispielsweise:

    /player/PlayerName
    /server/ServerName

    +HTML ERRORS - UNKNOWN_PAGE_404 || Stelle sicher, dass du einen Link benutzt, der von einem Befehl generiert wurde. Beispielsweise:

    /player/PlayerName
    /server/ServerName

    HTML ERRORS - UUID_404 || Die UUID des Spielers wurde nicht in der Datenbank gefunden. In Depth Help - /plan ? || > §2Hauptbefehl\ Zugriff auf Unterbefehle und Hilfe\ §2/plan §fListe Unterbefehle\ §2/plan ? §fAusführliche Hilfe In Depth Help - /plan analyze ? || > §2Analysebefehl\ Aktualisiert die Serverseite und stellt einen Zugriffslink bereit. From cbb45bf49dc1394f0fd1c020d96d06454443ecb0 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sun, 23 Sep 2018 17:39:15 +0300 Subject: [PATCH 63/76] [Fix] Locale lang group replacement changes Changed the Lang that was being used to replace things on the html, now some of the Lang is no longer used. (CmdHelpLang, CommandLang, etc) GenericLang caused page breakage due to wrong replacements, so it is no longer used for replacement Affected issues: Fixes #706 --- .../djrapitops/plan/system/locale/Locale.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/locale/Locale.java b/Plan/src/main/java/com/djrapitops/plan/system/locale/Locale.java index 3483cd1aa..baa310fc9 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/locale/Locale.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/locale/Locale.java @@ -1,14 +1,16 @@ package com.djrapitops.plan.system.locale; import com.djrapitops.plan.PlanPlugin; -import com.djrapitops.plan.system.locale.lang.Lang; +import com.djrapitops.plan.system.locale.lang.*; import com.djrapitops.plan.system.settings.Settings; import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -71,10 +73,22 @@ public class Locale extends HashMap { String replaced = from; - // Longest first so that entries that contain each other don't partially replace. - List> entries = entrySet().stream().sorted( - (one, two) -> Integer.compare(two.getKey().getIdentifier().length(), one.getKey().getIdentifier().length()) - ).collect(Collectors.toList()); + Lang[][] langs = new Lang[][]{ + NetworkPageLang.values(), + PlayerPageLang.values(), + ServerPageLang.values(), + CommonHtmlLang.values() + }; + + List> entries = Arrays.stream(langs) + .flatMap(Arrays::stream) + .collect(Collectors.toMap(Function.identity(), this::get)) + .entrySet().stream() + // Longest first so that entries that contain each other don't partially replace. + .sorted((one, two) -> Integer.compare( + two.getKey().getIdentifier().length(), + one.getKey().getIdentifier().length() + )).collect(Collectors.toList()); for (Entry entry : entries) { String defaultValue = entry.getKey().getDefault(); From d1d27534222f3a358ac8208caf0b15aa5d529c8b Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sun, 23 Sep 2018 17:51:36 +0300 Subject: [PATCH 64/76] [Fix] Fix MySQL query for "Has Column" MySQL query for has column did not take database name into account. This means that a database with multiple Plan databases would provide wrong results for the Patch system, leading to patch failiure Affected issues: Fixes #732 --- .../plan/system/database/databases/sql/patches/Patch.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java index 39ed66ed7..cb4fcbb28 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java @@ -52,11 +52,12 @@ public abstract class Patch { protected boolean hasColumn(String tableName, String columnName) { return usingMySQL ? query(new QueryStatement("SELECT * FROM information_schema.COLUMNS" + - " WHERE TABLE_NAME=? AND COLUMN_NAME=?") { + " WHERE TABLE_NAME=? AND COLUMN_NAME=? AND TABLE_SCHEMA=?") { @Override public void prepare(PreparedStatement statement) throws SQLException { statement.setString(1, tableName); statement.setString(2, columnName); + statement.setString(3, Settings.DB_DATABASE.toString()); } @Override From 836bf28348a9c4fff9a6ad48058ce20ba1a15119 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sun, 23 Sep 2018 17:52:33 +0300 Subject: [PATCH 65/76] [V] Version bump to 4.4.6 --- Plan/src/main/java/com/djrapitops/plan/PlanSponge.java | 2 +- Plan/src/main/resources/bungee.yml | 2 +- Plan/src/main/resources/plugin.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java b/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java index 543b60759..aa5dc60fd 100644 --- a/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java +++ b/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java @@ -25,7 +25,7 @@ import org.spongepowered.api.plugin.Plugin; import java.io.File; import java.io.InputStream; -@Plugin(id = "plan", name = "Plan", version = "4.4.5", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"}) +@Plugin(id = "plan", name = "Plan", version = "4.4.6", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"}) public class PlanSponge extends SpongePlugin implements PlanPlugin { @Inject diff --git a/Plan/src/main/resources/bungee.yml b/Plan/src/main/resources/bungee.yml index 0b1e9b6f6..048b498cc 100644 --- a/Plan/src/main/resources/bungee.yml +++ b/Plan/src/main/resources/bungee.yml @@ -1,4 +1,4 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.PlanBungee -version: 4.4.5 \ No newline at end of file +version: 4.4.6 \ No newline at end of file diff --git a/Plan/src/main/resources/plugin.yml b/Plan/src/main/resources/plugin.yml index 4b1b783fd..56ef3675e 100644 --- a/Plan/src/main/resources/plugin.yml +++ b/Plan/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.Plan -version: 4.4.5 +version: 4.4.6 softdepend: - EssentialsX - Towny From e72b89571ee6029dc86d455ad53b0718a741ad77 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sun, 23 Sep 2018 18:04:03 +0300 Subject: [PATCH 66/76] [Fix] Order of Registration to PluginData values Old addValue method added the values in an arbitrary label order, Order of adding makes more sense and gives developers more control. --- .../plan/data/element/InspectContainer.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/data/element/InspectContainer.java b/Plan/src/main/java/com/djrapitops/plan/data/element/InspectContainer.java index 9b2f6fa60..f3a5b7b14 100644 --- a/Plan/src/main/java/com/djrapitops/plan/data/element/InspectContainer.java +++ b/Plan/src/main/java/com/djrapitops/plan/data/element/InspectContainer.java @@ -5,6 +5,8 @@ package com.djrapitops.plan.data.element; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -25,18 +27,18 @@ import java.util.TreeMap; */ public class InspectContainer { - protected TreeMap values; + protected List values; protected TreeMap html; protected TreeMap tables; public InspectContainer() { - values = new TreeMap<>(); + values = new ArrayList<>(); html = new TreeMap<>(); tables = new TreeMap<>(); } public final void addValue(String label, Serializable value) { - values.put(label, value.toString()); + values.add(label + ": " + value.toString()); } public final void addHtml(String key, String html) { @@ -52,8 +54,8 @@ public class InspectContainer { if (!values.isEmpty()) { parsed.append("
    "); - for (Map.Entry entry : values.entrySet()) { - parsed.append("

    ").append(entry.getKey()).append(": ").append(entry.getValue()).append("

    "); + for (String value : values) { + parsed.append("

    ").append(value).append("

    "); } parsed.append("
    "); } From 81969682a48a1bee81e5284dc2f5e7aad9f1fd40 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Mon, 24 Sep 2018 19:21:06 +0300 Subject: [PATCH 67/76] Update versions.txt --- versions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/versions.txt b/versions.txt index f175248c3..ca64eda6a 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,4 @@ +REL|4.4.6|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.6-REL/Plan-4.4.6-bukkit-bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.6-REL DEV|4.4.5-b2|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.6-DEV2/Plan-4.4.5-b2-bukkit-bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.6-DEV2 REL|4.4.5|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.5-RELEASE/Plan-4.4.5.Bukkit.Bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.5-RELEASE REL|4.4.4|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.4-REL/Plan-4.4.4.jar|https://www.spigotmc.org/resources/plan-player-analytics.32536/update?update=240584 From 92ce37dda361677f9b55a1de98f23d0cbca99264 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Wed, 26 Sep 2018 20:40:41 +0300 Subject: [PATCH 68/76] [Fix] Patch System column addition fixes This affects KillsServerIDPatch and IPHashPatch issues. Affected issues: #732 --- .../databases/sql/patches/GeoInfoLastUsedPatch.java | 2 +- .../system/database/databases/sql/patches/IPHashPatch.java | 2 +- .../database/databases/sql/patches/KillsServerIDPatch.java | 2 +- .../databases/sql/patches/NicknameLastSeenPatch.java | 2 +- .../plan/system/database/databases/sql/patches/Patch.java | 7 ++----- .../databases/sql/patches/SessionAFKTimePatch.java | 2 +- .../databases/sql/patches/TransferPartitionPatch.java | 2 +- 7 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/GeoInfoLastUsedPatch.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/GeoInfoLastUsedPatch.java index 80b0d2ef5..d4ab90098 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/GeoInfoLastUsedPatch.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/GeoInfoLastUsedPatch.java @@ -16,7 +16,7 @@ public class GeoInfoLastUsedPatch extends Patch { @Override public void apply() { - addColumns(GeoInfoTable.TABLE_NAME, + addColumn(GeoInfoTable.TABLE_NAME, GeoInfoTable.Col.LAST_USED + " bigint NOT NULL DEFAULT 0" ); } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/IPHashPatch.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/IPHashPatch.java index a83134639..8f4b675ea 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/IPHashPatch.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/IPHashPatch.java @@ -16,6 +16,6 @@ public class IPHashPatch extends Patch { @Override public void apply() { - addColumns(GeoInfoTable.Col.IP_HASH.get() + " varchar(200) DEFAULT ''"); + addColumn(GeoInfoTable.TABLE_NAME, GeoInfoTable.Col.IP_HASH.get() + " varchar(200) DEFAULT ''"); } } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/KillsServerIDPatch.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/KillsServerIDPatch.java index e6832a7fd..8428524da 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/KillsServerIDPatch.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/KillsServerIDPatch.java @@ -40,7 +40,7 @@ public class KillsServerIDPatch extends Patch { @Override public void apply() { - addColumns(KillsTable.Col.SERVER_ID + " integer NOT NULL DEFAULT 0"); + addColumn(KillsTable.TABLE_NAME, KillsTable.Col.SERVER_ID + " integer NOT NULL DEFAULT 0"); Map sessionIDServerIDRelation = db.getSessionsTable().getIDServerIDRelation(); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/NicknameLastSeenPatch.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/NicknameLastSeenPatch.java index a45bc697b..a9155de2f 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/NicknameLastSeenPatch.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/NicknameLastSeenPatch.java @@ -25,7 +25,7 @@ public class NicknameLastSeenPatch extends Patch { @Override public void apply() { - addColumns(NicknamesTable.TABLE_NAME, + addColumn(NicknamesTable.TABLE_NAME, NicknamesTable.Col.LAST_USED + " bigint NOT NULL DEFAULT '0'" ); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java index cb4fcbb28..9ad3c26b5 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/Patch.java @@ -78,11 +78,8 @@ public abstract class Patch { }); } - protected void addColumns(String tableName, String... columnInfo) { - for (int i = 0; i < columnInfo.length; i++) { - columnInfo[i] = "ALTER TABLE " + tableName + " ADD " + (usingMySQL ? "" : "COLUMN ") + columnInfo[i]; - } - db.executeUnsafe(columnInfo); + protected void addColumn(String tableName, String columnInfo) { + db.executeUnsafe("ALTER TABLE " + tableName + " ADD " + (usingMySQL ? "" : "COLUMN ") + columnInfo); } protected void dropTable(String name) { diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/SessionAFKTimePatch.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/SessionAFKTimePatch.java index 672780d3c..5e8a28032 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/SessionAFKTimePatch.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/SessionAFKTimePatch.java @@ -16,7 +16,7 @@ public class SessionAFKTimePatch extends Patch { @Override public void apply() { - addColumns(SessionsTable.TABLE_NAME, + addColumn(SessionsTable.TABLE_NAME, SessionsTable.Col.AFK_TIME + " bigint NOT NULL DEFAULT 0" ); } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/TransferPartitionPatch.java b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/TransferPartitionPatch.java index 147d8e47a..eea4affe9 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/TransferPartitionPatch.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/databases/sql/patches/TransferPartitionPatch.java @@ -16,6 +16,6 @@ public class TransferPartitionPatch extends Patch { @Override public void apply() { - addColumns(TransferTable.TABLE_NAME, TransferTable.Col.PART + " bigint NOT NULL DEFAULT 0"); + addColumn(TransferTable.TABLE_NAME, TransferTable.Col.PART + " bigint NOT NULL DEFAULT 0"); } } From 93951f1f254145ef20aec5eee75d73b2074081d1 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Wed, 26 Sep 2018 20:43:49 +0300 Subject: [PATCH 69/76] [Fix] Increased database clean task start delay 1 second delay is not enough when a patch system has not applied all patches, and some data requiring patching is scheduled for removal. Affected issues: #732 --- .../main/java/com/djrapitops/plan/system/database/DBSystem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/DBSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/database/DBSystem.java index 21eb8bfca..a6aa0970d 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/DBSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/DBSystem.java @@ -81,7 +81,7 @@ public abstract class DBSystem implements SubSystem { try { Benchmark.start("Init Database"); initDatabase(); - db.scheduleClean(1L); + db.scheduleClean(20L); Log.info(locale.get().getString(PluginLang.ENABLED_DATABASE, db.getName())); Benchmark.stop("Enable", "Init Database"); } catch (DBInitException e) { From a18dc2d3f24773817c9cb6507a5c712c183b4fb3 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 29 Sep 2018 12:16:07 +0300 Subject: [PATCH 70/76] [Fix] Prevented NPE due in PluginData This exception was caused by analysisData being null during PluginData analysis - This should not occur unless two analysis are being performed concurrently & first analysis finishes and clears the variable. Additional work is required for preventing two concurrent analysis. Affected issues: #711 --- .../pluginbridge/plan/factions/FactionsData.java | 3 ++- .../pluginbridge/plan/kingdoms/KingdomsData.java | 5 +++-- .../pluginbridge/plan/litebans/LiteBansData.java | 3 ++- .../djrapitops/pluginbridge/plan/towny/TownyData.java | 3 ++- .../pluginbridge/plan/vault/VaultEcoData.java | 10 +++++----- .../pluginbridge/plan/vault/VaultPermData.java | 10 +++++----- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/factions/FactionsData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/factions/FactionsData.java index b6b63c650..b3e017a88 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/factions/FactionsData.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/factions/FactionsData.java @@ -72,7 +72,8 @@ public class FactionsData extends PluginData { if (!factions.isEmpty()) { FactionsAccordion factionsAccordion = new FactionsAccordion( factions, - analysisData.getValue(AnalysisKeys.PLAYERS_MUTATOR).orElse(new PlayersMutator(new ArrayList<>())) + Optional.ofNullable(analysisData).flatMap(c -> c.getValue(AnalysisKeys.PLAYERS_MUTATOR)) + .orElse(new PlayersMutator(new ArrayList<>())) ); analysisContainer.addHtml("factionAccordion", factionsAccordion.toHtml()); diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/kingdoms/KingdomsData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/kingdoms/KingdomsData.java index 53e97c965..7e842e0cf 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/kingdoms/KingdomsData.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/kingdoms/KingdomsData.java @@ -1,4 +1,4 @@ -/* +/* * Licence is provided in the jar as license.yml also here: * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml */ @@ -62,7 +62,8 @@ public class KingdomsData extends PluginData { if (!kingdoms.isEmpty()) { KingdomsAccordion kingdomsAccordion = new KingdomsAccordion( kingdoms, - analysisData.getValue(AnalysisKeys.PLAYERS_MUTATOR).orElse(new PlayersMutator(new ArrayList<>())) + Optional.ofNullable(analysisData).flatMap(c -> c.getValue(AnalysisKeys.PLAYERS_MUTATOR)) + .orElse(new PlayersMutator(new ArrayList<>())) ); analysisContainer.addHtml("kingdomsAccordion", kingdomsAccordion.toHtml()); diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansData.java index f58704be9..92dfadfa9 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansData.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/litebans/LiteBansData.java @@ -179,7 +179,8 @@ public class LiteBansData extends PluginData implements BanData { if (objects.isEmpty()) { table.addRow("No Data"); } else { - Map playerNames = analysisData.getValue(AnalysisKeys.PLAYER_NAMES).orElse(new HashMap<>()); + Map playerNames = Optional.ofNullable(analysisData) + .flatMap(c -> c.getValue(AnalysisKeys.PLAYER_NAMES)).orElse(new HashMap<>()); for (LiteBansDBObj object : objects) { UUID uuid = object.getUuid(); String name = playerNames.getOrDefault(uuid, uuid.toString()); diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/towny/TownyData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/towny/TownyData.java index bacdd88ec..83c902822 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/towny/TownyData.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/towny/TownyData.java @@ -97,7 +97,8 @@ public class TownyData extends PluginData { TownsAccordion townsAccordion = new TownsAccordion( towns, - analysisData.getValue(AnalysisKeys.PLAYERS_MUTATOR).orElse(new PlayersMutator(new ArrayList<>())) + Optional.ofNullable(analysisData).flatMap(c -> c.getValue(AnalysisKeys.PLAYERS_MUTATOR)) + .orElse(new PlayersMutator(new ArrayList<>())) ); analysisContainer.addHtml("townAccordion", townsAccordion.toHtml()); diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/vault/VaultEcoData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/vault/VaultEcoData.java index 175672de1..92c5047fc 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/vault/VaultEcoData.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/vault/VaultEcoData.java @@ -9,7 +9,6 @@ import com.djrapitops.plan.data.element.InspectContainer; import com.djrapitops.plan.data.plugin.ContainerSize; import com.djrapitops.plan.data.plugin.PluginData; import com.djrapitops.plan.data.store.keys.AnalysisKeys; -import com.djrapitops.plan.data.store.mutators.PlayersMutator; import com.djrapitops.plan.system.cache.DataCache; import com.djrapitops.plan.utilities.FormatUtils; import com.djrapitops.plan.utilities.html.icon.Color; @@ -50,10 +49,11 @@ public class VaultEcoData extends PluginData { @Override public AnalysisContainer getServerData(Collection collection, AnalysisContainer analysisContainer) { - List offlinePlayers = analysisData.getValue(AnalysisKeys.PLAYERS_MUTATOR) - .map(PlayersMutator::all).orElse(new ArrayList<>()) - .stream().map(FakeOfflinePlayer::new) - .collect(Collectors.toList()); + List offlinePlayers = Optional.ofNullable(analysisData) + .flatMap(c -> c.getValue(AnalysisKeys.PLAYERS_MUTATOR)) + .map(mutator -> mutator.all().stream().map(FakeOfflinePlayer::new) + .collect(Collectors.toList())) + .orElse(new ArrayList<>()); Map balances = new HashMap<>(); double totalBalance = 0.0; diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/vault/VaultPermData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/vault/VaultPermData.java index 1e9d83dce..e2701c6b2 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/vault/VaultPermData.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/vault/VaultPermData.java @@ -9,7 +9,6 @@ import com.djrapitops.plan.data.element.InspectContainer; import com.djrapitops.plan.data.plugin.ContainerSize; import com.djrapitops.plan.data.plugin.PluginData; import com.djrapitops.plan.data.store.keys.AnalysisKeys; -import com.djrapitops.plan.data.store.mutators.PlayersMutator; import com.djrapitops.plan.system.cache.DataCache; import com.djrapitops.pluginbridge.plan.FakeOfflinePlayer; import net.milkbowl.vault.permission.Permission; @@ -49,10 +48,11 @@ public class VaultPermData extends PluginData { @Override public AnalysisContainer getServerData(Collection collection, AnalysisContainer analysisContainer) { - List offlinePlayers = analysisData.getValue(AnalysisKeys.PLAYERS_MUTATOR) - .map(PlayersMutator::all).orElse(new ArrayList<>()) - .stream().map(FakeOfflinePlayer::new) - .collect(Collectors.toList()); + List offlinePlayers = Optional.ofNullable(analysisData) + .flatMap(c -> c.getValue(AnalysisKeys.PLAYERS_MUTATOR)) + .map(mutator -> mutator.all().stream().map(FakeOfflinePlayer::new) + .collect(Collectors.toList())) + .orElse(new ArrayList<>()); Map groups = new HashMap<>(); for (FakeOfflinePlayer p : offlinePlayers) { From bca13717741a69582931580b2b8c15169ba9bdc5 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 29 Sep 2018 12:17:28 +0300 Subject: [PATCH 71/76] [V] Updated PluginBridge jar --- PlanPluginBridge/PlanPluginBridge-4.4.0.jar | Bin 169161 -> 170155 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/PlanPluginBridge/PlanPluginBridge-4.4.0.jar b/PlanPluginBridge/PlanPluginBridge-4.4.0.jar index 25b26975718cde71b772644ae2520761400953a7..6e1a2c5da8518244256f153e76e77a19f4848133 100644 GIT binary patch delta 26261 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 From a33ca5b004f7ce1b222421db789e4f8b7df4c424 Mon Sep 17 00:00:00 2001 From: Vankka Date: Thu, 4 Oct 2018 20:32:34 +0300 Subject: [PATCH 72/76] LuckPerms Plugin Data by Vankka (#742) --- .../java/com/djrapitops/plan/PlanSponge.java | 13 +- Plan/src/main/resources/bungee.yml | 7 +- Plan/src/main/resources/plugin.yml | 1 + PlanPluginBridge/pom.xml | 12 ++ .../djrapitops/pluginbridge/plan/Bridge.java | 4 + .../plan/luckperms/LuckPermsData.java | 125 ++++++++++++++++++ .../plan/luckperms/LuckPermsHook.java | 27 ++++ 7 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/luckperms/LuckPermsData.java create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/luckperms/LuckPermsHook.java diff --git a/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java b/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java index aa5dc60fd..e914ed0a8 100644 --- a/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java +++ b/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java @@ -20,12 +20,23 @@ import org.spongepowered.api.config.ConfigDir; import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.game.state.GameStartedServerEvent; import org.spongepowered.api.event.game.state.GameStoppingServerEvent; +import org.spongepowered.api.plugin.Dependency; import org.spongepowered.api.plugin.Plugin; import java.io.File; import java.io.InputStream; -@Plugin(id = "plan", name = "Plan", version = "4.4.6", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"}) +@Plugin( + id = "plan", + name = "Plan", + version = "4.4.6", + description = "Player Analytics Plugin by Rsl1122", + authors = {"Rsl1122"}, + dependencies = { + @Dependency(id = "nucleus", optional = true), + @Dependency(id = "luckperms", optional = true) + } +) public class PlanSponge extends SpongePlugin implements PlanPlugin { @Inject diff --git a/Plan/src/main/resources/bungee.yml b/Plan/src/main/resources/bungee.yml index 048b498cc..dd8f8c9ce 100644 --- a/Plan/src/main/resources/bungee.yml +++ b/Plan/src/main/resources/bungee.yml @@ -1,4 +1,9 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.PlanBungee -version: 4.4.6 \ No newline at end of file +version: 4.4.6 +softdepend: +- AdvancedBan +- LiteBans +- LuckPerms +- ViaVersion \ No newline at end of file diff --git a/Plan/src/main/resources/plugin.yml b/Plan/src/main/resources/plugin.yml index 56ef3675e..d66bbf18c 100644 --- a/Plan/src/main/resources/plugin.yml +++ b/Plan/src/main/resources/plugin.yml @@ -20,6 +20,7 @@ softdepend: - Kingdoms - RedProtect - AdvancedBan +- LuckPerms commands: plan: diff --git a/PlanPluginBridge/pom.xml b/PlanPluginBridge/pom.xml index a4f68f61d..d61b0fe23 100644 --- a/PlanPluginBridge/pom.xml +++ b/PlanPluginBridge/pom.xml @@ -94,6 +94,12 @@ 4.3.0-SNAPSHOT provided + + org.apache.commons + commons-text + 1.3 + + @@ -167,6 +173,12 @@ 1.6.0-PR1-S7.0 provided + + me.lucko.luckperms + luckperms-api + 4.3 + provided + diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java index 2adf0dfbd..fc835bacd 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java @@ -18,6 +18,7 @@ import com.djrapitops.pluginbridge.plan.jobs.JobsHook; import com.djrapitops.pluginbridge.plan.kingdoms.KingdomsHook; import com.djrapitops.pluginbridge.plan.litebans.LiteBansBukkitHook; import com.djrapitops.pluginbridge.plan.litebans.LiteBansBungeeHook; +import com.djrapitops.pluginbridge.plan.luckperms.LuckPermsHook; import com.djrapitops.pluginbridge.plan.mcmmo.McmmoHook; import com.djrapitops.pluginbridge.plan.nucleus.NucleusHook; import com.djrapitops.pluginbridge.plan.protocolsupport.ProtocolSupportHook; @@ -75,6 +76,7 @@ public class Bridge { private static Hook[] getSpongeHooks(HookHandler h) { return new Hook[]{ new BuyCraftHook(h), + new LuckPermsHook(h), new SpongeEconomyHook(h), new NucleusHook(h) }; @@ -85,6 +87,7 @@ public class Bridge { new AdvancedBanHook(h), new BuyCraftHook(h), new LiteBansBungeeHook(h), + new LuckPermsHook(h), new ViaVersionBungeeHook(h) }; } @@ -104,6 +107,7 @@ public class Bridge { new JobsHook(h), new KingdomsHook(h), new LiteBansBukkitHook(h), + new LuckPermsHook(h), new McmmoHook(h), new SuperbVoteHook(h), new ProtocolSupportHook(h), diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/luckperms/LuckPermsData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/luckperms/LuckPermsData.java new file mode 100644 index 000000000..9c10055ec --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/luckperms/LuckPermsData.java @@ -0,0 +1,125 @@ +/* + * Licence is provided in the jar as license.yml also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml + */ +package com.djrapitops.pluginbridge.plan.luckperms; + +import com.djrapitops.plan.data.element.AnalysisContainer; +import com.djrapitops.plan.data.element.InspectContainer; +import com.djrapitops.plan.data.element.TableContainer; +import com.djrapitops.plan.data.plugin.ContainerSize; +import com.djrapitops.plan.data.plugin.PluginData; +import com.djrapitops.plan.utilities.html.icon.Color; +import com.djrapitops.plan.utilities.html.icon.Family; +import com.djrapitops.plan.utilities.html.icon.Icon; +import java.util.*; +import java.util.stream.Collectors; +import me.lucko.luckperms.api.*; +import me.lucko.luckperms.api.caching.MetaData; +import org.apache.commons.text.TextStringBuilder; + +/** + * PluginData for LuckPerms plugin. + * + * @author Vankka + */ +public class LuckPermsData extends PluginData { + private LuckPermsApi api; + + public LuckPermsData(LuckPermsApi api) { + super(ContainerSize.THIRD, "LuckPerms"); + setPluginIcon(Icon.called("exclamation-triangle").of(Color.LIGHT_GREEN).build()); + + this.api = api; + } + + @Override + public InspectContainer getPlayerData(UUID uuid, InspectContainer inspectContainer) { + User user = api.getUser(uuid); + + if (user == null) { + inspectContainer.addValue("Data unavailable", "Could not get user data"); + return inspectContainer; + } + + MetaData metaData = user.getCachedData().getMetaData(Contexts.allowAll()); + String prefix = metaData.getPrefix(); + String suffix = metaData.getSuffix(); + + inspectContainer.addValue(getWithIcon("Primary group", Icon.called("user-friends").of(Family.SOLID)), user.getPrimaryGroup()); + inspectContainer.addValue(getWithIcon("Prefix", Icon.called("file-signature").of(Family.SOLID).of(Color.GREEN)), prefix != null ? prefix : "None"); + inspectContainer.addValue(getWithIcon("Suffix", Icon.called("file-signature").of(Family.SOLID).of(Color.BLUE)),suffix != null ? suffix : "None"); + + if (!metaData.getMeta().isEmpty()) { + TableContainer metaTable = new TableContainer( + getWithIcon("Meta", Icon.called("info-circle").of(Family.SOLID)), + getWithIcon("Value", Icon.called("file-alt").of(Family.SOLID)) + ); + metaData.getMeta().forEach((key, value) -> metaTable.addRow(key, value)); + inspectContainer.addTable("Meta", metaTable); + } + + List groups = user.getPermissions().stream() + .filter(Node::isGroupNode) + .map(Node::getGroupName) + .sorted() + .collect(Collectors.toList()); + + inspectContainer.addValue( + getWithIcon("Groups", Icon.called("user-friends").of(Family.SOLID)), + new TextStringBuilder().appendWithSeparators(groups, ", ").build() + ); + + Set tracks = api.getTracks(); + if (!tracks.isEmpty()) { + TableContainer trackTable = new TableContainer( + getWithIcon("Track", Icon.called("ellipsis-h").of(Family.SOLID)), + getWithIcon("Group", Icon.called("user-friends").of(Family.SOLID)) + ); + for (Track track : tracks) { + // reduce is used to get the last element + String currentGroup = api.getGroups().stream() + .map(this::getGroupDisplayName).filter(groups::contains) + .reduce((first, second) -> second).orElse("None"); + trackTable.addRow(track.getName(), currentGroup); + } + inspectContainer.addTable("Tracks", trackTable); + } + + return inspectContainer; + } + + @Override + public AnalysisContainer getServerData(Collection uuids, AnalysisContainer analysisContainer) { + // There will *always* be atleast 1 group + TableContainer groupTable = new TableContainer( + getWithIcon("Group", Icon.called("user-friends").of(Family.SOLID)), + getWithIcon("Weight", Icon.called("weight-hanging").of(Family.SOLID)), + getWithIcon("Permissions", Icon.called("list").of(Family.SOLID)) + ); + + api.getGroups().stream().sorted(Comparator.comparing(Group::getName)).forEach(group -> { + OptionalInt weight = group.getWeight(); + + groupTable.addRow(getGroupDisplayName(group), weight.isPresent() ? weight.getAsInt() : "None", group.getPermissions().size()); + }); + analysisContainer.addTable("Groups", groupTable); + + Set tracks = api.getTracks(); + if (!tracks.isEmpty()) { + TableContainer trackTable = new TableContainer( + getWithIcon("Track", Icon.called("ellipsis-h").of(Family.SOLID)), + getWithIcon("Size", Icon.called("list").of(Family.SOLID)) + ); + tracks.forEach(track -> trackTable.addRow(track.getName(), track.getSize())); + analysisContainer.addTable("Tracks", trackTable); + } + + return analysisContainer; + } + + private String getGroupDisplayName(Group group) { + String displayName = group.getDisplayName(); + return displayName != null ? displayName : group.getName(); + } +} diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/luckperms/LuckPermsHook.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/luckperms/LuckPermsHook.java new file mode 100644 index 000000000..341ed1853 --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/luckperms/LuckPermsHook.java @@ -0,0 +1,27 @@ +/* + * Licence is provided in the jar as license.yml also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml + */ +package com.djrapitops.pluginbridge.plan.luckperms; + +import com.djrapitops.plan.data.plugin.HookHandler; +import com.djrapitops.pluginbridge.plan.Hook; +import me.lucko.luckperms.LuckPerms; + +/** + * Hook for LuckPerms plugin. + * + * @author Vankka + */ +public class LuckPermsHook extends Hook { + public LuckPermsHook(HookHandler hookHandler) { + super("me.lucko.luckperms.LuckPerms", hookHandler); + } + + @Override + public void hook() throws IllegalStateException { + if (enabled) { + addPluginDataSource(new LuckPermsData(LuckPerms.getApi())); + } + } +} From 27c2492599631c4e2475dc0f2dad51f49842d60d Mon Sep 17 00:00:00 2001 From: Vankka Date: Fri, 5 Oct 2018 22:30:57 +0300 Subject: [PATCH 73/76] DiscordSRV plugin data by Vankka (#743) --- Plan/src/main/resources/plugin.yml | 3 +- PlanPluginBridge/pom.xml | 10 ++ .../djrapitops/pluginbridge/plan/Bridge.java | 2 + .../plan/discordsrv/DiscordSRVData.java | 124 ++++++++++++++++++ .../plan/discordsrv/DiscordSRVHook.java | 26 ++++ 5 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/discordsrv/DiscordSRVData.java create mode 100644 PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/discordsrv/DiscordSRVHook.java diff --git a/Plan/src/main/resources/plugin.yml b/Plan/src/main/resources/plugin.yml index d66bbf18c..436213abd 100644 --- a/Plan/src/main/resources/plugin.yml +++ b/Plan/src/main/resources/plugin.yml @@ -21,6 +21,7 @@ softdepend: - RedProtect - AdvancedBan - LuckPerms +- DiscordSRV commands: plan: @@ -89,7 +90,7 @@ permissions: default: op plan.webmanage: description: Manage the webusers, delete, list, check - default: op; + default: op plan.basic: children: diff --git a/PlanPluginBridge/pom.xml b/PlanPluginBridge/pom.xml index d61b0fe23..329b230d8 100644 --- a/PlanPluginBridge/pom.xml +++ b/PlanPluginBridge/pom.xml @@ -85,6 +85,10 @@ nucleus-repo http://repo.drnaylor.co.uk/artifactory/list/minecraft + + discordsrv-repo + https://ci.scarsz.me/plugin/repository/everything/ + @@ -179,6 +183,12 @@ 4.3 provided + + github.scarsz.discordsrv + DiscordSRV + 1.16.4 + provided + diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java index fc835bacd..4ac01895d 100644 --- a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/Bridge.java @@ -10,6 +10,7 @@ import com.djrapitops.pluginbridge.plan.advancedban.AdvancedBanHook; import com.djrapitops.pluginbridge.plan.askyblock.ASkyBlockHook; import com.djrapitops.pluginbridge.plan.banmanager.BanManagerHook; import com.djrapitops.pluginbridge.plan.buycraft.BuyCraftHook; +import com.djrapitops.pluginbridge.plan.discordsrv.DiscordSRVHook; import com.djrapitops.pluginbridge.plan.essentials.EssentialsHook; import com.djrapitops.pluginbridge.plan.factions.FactionsHook; import com.djrapitops.pluginbridge.plan.griefprevention.GriefPreventionHook; @@ -100,6 +101,7 @@ public class Bridge { new ASkyBlockHook(h), new BanManagerHook(h), new BuyCraftHook(h), + new DiscordSRVHook(h), new EssentialsHook(h), new FactionsHook(h), new GriefPreventionHook(h), diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/discordsrv/DiscordSRVData.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/discordsrv/DiscordSRVData.java new file mode 100644 index 000000000..0223833ca --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/discordsrv/DiscordSRVData.java @@ -0,0 +1,124 @@ +/* + * Licence is provided in the jar as license.yml also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml + */ +package com.djrapitops.pluginbridge.plan.discordsrv; + +import com.djrapitops.plan.data.element.AnalysisContainer; +import com.djrapitops.plan.data.element.InspectContainer; +import com.djrapitops.plan.data.plugin.ContainerSize; +import com.djrapitops.plan.data.plugin.PluginData; +import com.djrapitops.plan.utilities.FormatUtils; +import com.djrapitops.plan.utilities.html.icon.Color; +import com.djrapitops.plan.utilities.html.icon.Family; +import com.djrapitops.plan.utilities.html.icon.Icon; +import github.scarsz.discordsrv.DiscordSRV; +import github.scarsz.discordsrv.dependencies.jda.core.entities.Member; +import github.scarsz.discordsrv.dependencies.jda.core.entities.Role; +import github.scarsz.discordsrv.dependencies.jda.core.entities.User; +import github.scarsz.discordsrv.util.DiscordUtil; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import org.apache.commons.text.TextStringBuilder; + +/** + * PluginData for DiscordSRV plugin. + * + * @author Vankka + */ +public class DiscordSRVData extends PluginData { + public DiscordSRVData() { + super(ContainerSize.THIRD, "DiscordSRV"); + setPluginIcon(Icon.called("discord").of(Family.BRAND).build()); + } + + @Override + public InspectContainer getPlayerData(UUID uuid, InspectContainer inspectContainer) { + if (!DiscordSRV.isReady) { + return inspectContainer; + } + + String userId = DiscordSRV.getPlugin().getAccountLinkManager().getDiscordId(uuid); + User user = userId != null ? DiscordUtil.getUserById(userId) : null; + + if (user == null) { + return inspectContainer; + } + + inspectContainer.addValue( + getWithIcon("Username", Icon.called("user").of(Family.SOLID).of(Color.CYAN)), + "@" + user.getName() + "#" + user.getDiscriminator() + ); + inspectContainer.addValue( + getWithIcon("Account creation date", Icon.called("plus").of(Family.SOLID).of(Color.BLUE)), + FormatUtils.formatTimeStampYear(user.getCreationTime().toEpochSecond() * 1000L) + ); + + Member member = DiscordSRV.getPlugin().getMainGuild().getMember(user); + + if (member != null) { + addMemberData(member, inspectContainer); + } + + return inspectContainer; + } + + private void addMemberData(Member member, InspectContainer inspectContainer) { + String nickname = member.getNickname(); + + inspectContainer.addValue( + getWithIcon("Nickname", Icon.called("user-ninja").of(Family.SOLID).of(Color.ORANGE)), + nickname != null ? nickname : "None" + ); + inspectContainer.addValue( + getWithIcon("Join Date", Icon.called("plus").of(Family.SOLID).of(Color.GREEN)), + FormatUtils.formatTimeStampYear(member.getJoinDate().toEpochSecond() * 1000L) + ); + + List roles = member.getRoles().stream().map(Role::getName).collect(Collectors.toList()); // Ordered list of role names + if (!roles.isEmpty()) { + inspectContainer.addValue( + getWithIcon("Roles", Icon.called("user-circle").of(Family.SOLID).of(Color.RED)), + new TextStringBuilder().appendWithSeparators(roles, ", ").build() + ); + } + } + + @Override + public AnalysisContainer getServerData(Collection uuids, AnalysisContainer analysisContainer) { + if (!DiscordSRV.isReady) { + return analysisContainer; + } + + int accountsLinked = DiscordSRV.getPlugin().getAccountLinkManager().getLinkedAccounts().size(); + int guildUsers = DiscordSRV.getPlugin().getMainGuild().getMembers().size(); + + analysisContainer.addValue( + getWithIcon("Accounts linked", Icon.called("link").of(Family.SOLID).of(Color.CYAN)), + accountsLinked + ); + analysisContainer.addValue( + getWithIcon("Users in main guild", Icon.called("users").of(Family.SOLID).of(Color.GREEN)), + guildUsers + ); + analysisContainer.addValue( + getWithIcon("Accounts linked / Total players", Icon.called("percentage").of(Family.SOLID).of(Color.TEAL)), + calculatePercentage(accountsLinked, uuids.size()) + "%" + ); + analysisContainer.addValue( + getWithIcon("Accounts linked / Users in main guild", Icon.called("percentage").of(Family.SOLID).of(Color.LIGHT_GREEN)), + calculatePercentage(accountsLinked, guildUsers) + "%" + ); + + return analysisContainer; + } + + private double calculatePercentage(int input1, int input2) { + if (input1 == 0 || input2 == 0) + return 0D; + + return Math.round((double) input1 / input2 * 10000D) / 100D; // 2 decimals + } +} diff --git a/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/discordsrv/DiscordSRVHook.java b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/discordsrv/DiscordSRVHook.java new file mode 100644 index 000000000..0c951b7c6 --- /dev/null +++ b/PlanPluginBridge/src/main/java/com/djrapitops/pluginbridge/plan/discordsrv/DiscordSRVHook.java @@ -0,0 +1,26 @@ +/* + * Licence is provided in the jar as license.yml also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml + */ +package com.djrapitops.pluginbridge.plan.discordsrv; + +import com.djrapitops.plan.data.plugin.HookHandler; +import com.djrapitops.pluginbridge.plan.Hook; + +/** + * Hook for DiscordSRV plugin. + * + * @author Vankka + */ +public class DiscordSRVHook extends Hook { + public DiscordSRVHook(HookHandler hookHandler) { + super("github.scarsz.discordsrv.DiscordSRV", hookHandler); + } + + @Override + public void hook() throws NoClassDefFoundError { + if (enabled) { + addPluginDataSource(new DiscordSRVData()); + } + } +} From dd3c297fd0a6d2ea4472df1e0d4e47d8772e70eb Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sat, 6 Oct 2018 21:56:58 +0300 Subject: [PATCH 74/76] [V] Increased version to 4.4.7 --- Plan/src/main/java/com/djrapitops/plan/PlanSponge.java | 2 +- Plan/src/main/resources/bungee.yml | 2 +- Plan/src/main/resources/plugin.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java b/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java index e914ed0a8..858ed6b6c 100644 --- a/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java +++ b/Plan/src/main/java/com/djrapitops/plan/PlanSponge.java @@ -29,7 +29,7 @@ import java.io.InputStream; @Plugin( id = "plan", name = "Plan", - version = "4.4.6", + version = "4.4.7", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"}, dependencies = { diff --git a/Plan/src/main/resources/bungee.yml b/Plan/src/main/resources/bungee.yml index dd8f8c9ce..b165e7633 100644 --- a/Plan/src/main/resources/bungee.yml +++ b/Plan/src/main/resources/bungee.yml @@ -1,7 +1,7 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.PlanBungee -version: 4.4.6 +version: 4.4.7 softdepend: - AdvancedBan - LiteBans diff --git a/Plan/src/main/resources/plugin.yml b/Plan/src/main/resources/plugin.yml index 436213abd..f83a18fa4 100644 --- a/Plan/src/main/resources/plugin.yml +++ b/Plan/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: Plan author: Rsl1122 main: com.djrapitops.plan.Plan -version: 4.4.6 +version: 4.4.7 softdepend: - EssentialsX - Towny From d57915e66c68416c6f1e5c071b3c8f7a9a6e9d84 Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sun, 7 Oct 2018 11:45:49 +0300 Subject: [PATCH 75/76] Update versions.txt --- versions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/versions.txt b/versions.txt index ca64eda6a..8890ce64b 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,4 @@ +REL|4.4.7|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.7-REL/Plan-4.4.7-bukkit-bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.7-REL REL|4.4.6|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.6-REL/Plan-4.4.6-bukkit-bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.6-REL DEV|4.4.5-b2|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.6-DEV2/Plan-4.4.5-b2-bukkit-bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.6-DEV2 REL|4.4.5|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/download/4.4.5-RELEASE/Plan-4.4.5.Bukkit.Bungee.jar|https://github.com/Rsl1122/Plan-PlayerAnalytics/releases/tag/4.4.5-RELEASE From 8065432f516d8903c77fc7dd2873b55b2cfc89a2 Mon Sep 17 00:00:00 2001 From: MicleBrick Date: Fri, 12 Oct 2018 14:34:47 -0400 Subject: [PATCH 76/76] [New] Velocity Support by Miclebrick (#740) This is a merge commit of a PR that introduces Velocity support. Some of this support has been ported to APF already and will be removed after merge. Thanks to Micklebrick and astei! :) --- Plan/pom.xml | 12 ++ .../com/djrapitops/plan/PlanVelocity.java | 136 ++++++++++++++++++ .../com/djrapitops/plan/api/VelocityAPI.java | 42 ++++++ .../plan/command/PlanVelocityCommand.java | 57 ++++++++ .../djrapitops/plan/system/BungeeSystem.java | 12 +- .../plan/system/VelocitySystem.java | 72 ++++++++++ .../plan/system/cache/BungeeCacheSystem.java | 18 --- .../plan/system/cache/ProxyCacheSystem.java | 18 +++ ...ngeeDataCache.java => ProxyDataCache.java} | 8 +- ...BungeeDBSystem.java => ProxyDBSystem.java} | 4 +- ...eeInfoSystem.java => ProxyInfoSystem.java} | 8 +- ...System.java => ProxyConnectionSystem.java} | 6 +- .../info/server/VelocityServerInfo.java | 88 ++++++++++++ .../properties/VelocityServerProperties.java | 28 ++++ .../listeners/VelocityListenerSystem.java | 23 +++ .../velocity/PlayerOnlineListener.java | 85 +++++++++++ .../plan/system/tasks/BungeeTaskSystem.java | 2 +- .../plan/system/tasks/VelocityTaskSystem.java | 49 +++++++ .../EnableConnectionTask.java | 2 +- .../tasks/server/PingCountTimerVelocity.java | 113 +++++++++++++++ .../tasks/velocity/VelocityTPSCountTimer.java | 31 ++++ .../com/djrapitops/plugin/VelocityPlugin.java | 101 +++++++++++++ .../command/velocity/VelocityCMDSender.java | 79 ++++++++++ .../command/velocity/VelocityCommand.java | 27 ++++ .../plan/system/cache/SessionCacheTest.java | 2 +- 25 files changed, 983 insertions(+), 40 deletions(-) create mode 100644 Plan/src/main/java/com/djrapitops/plan/PlanVelocity.java create mode 100644 Plan/src/main/java/com/djrapitops/plan/api/VelocityAPI.java create mode 100644 Plan/src/main/java/com/djrapitops/plan/command/PlanVelocityCommand.java create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/VelocitySystem.java delete mode 100644 Plan/src/main/java/com/djrapitops/plan/system/cache/BungeeCacheSystem.java create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/cache/ProxyCacheSystem.java rename Plan/src/main/java/com/djrapitops/plan/system/cache/{BungeeDataCache.java => ProxyDataCache.java} (63%) rename Plan/src/main/java/com/djrapitops/plan/system/database/{BungeeDBSystem.java => ProxyDBSystem.java} (87%) rename Plan/src/main/java/com/djrapitops/plan/system/info/{BungeeInfoSystem.java => ProxyInfoSystem.java} (91%) rename Plan/src/main/java/com/djrapitops/plan/system/info/connection/{BungeeConnectionSystem.java => ProxyConnectionSystem.java} (95%) create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/info/server/VelocityServerInfo.java create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/info/server/properties/VelocityServerProperties.java create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/listeners/VelocityListenerSystem.java create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/listeners/velocity/PlayerOnlineListener.java create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/tasks/VelocityTaskSystem.java rename Plan/src/main/java/com/djrapitops/plan/system/tasks/{bungee => proxy}/EnableConnectionTask.java (93%) create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerVelocity.java create mode 100644 Plan/src/main/java/com/djrapitops/plan/system/tasks/velocity/VelocityTPSCountTimer.java create mode 100644 Plan/src/main/java/com/djrapitops/plugin/VelocityPlugin.java create mode 100644 Plan/src/main/java/com/djrapitops/plugin/command/velocity/VelocityCMDSender.java create mode 100644 Plan/src/main/java/com/djrapitops/plugin/command/velocity/VelocityCommand.java diff --git a/Plan/pom.xml b/Plan/pom.xml index 316381126..b70ade3e8 100644 --- a/Plan/pom.xml +++ b/Plan/pom.xml @@ -24,6 +24,10 @@ sponge-repo https://repo.spongepowered.org/maven + + velocity-repo + https://repo.velocitypowered.com/snapshots/ + md_5-snapshots @@ -79,6 +83,14 @@ jar provided + + + com.velocitypowered + velocity-api + 1.0-SNAPSHOT + jar + provided + org.spongepowered diff --git a/Plan/src/main/java/com/djrapitops/plan/PlanVelocity.java b/Plan/src/main/java/com/djrapitops/plan/PlanVelocity.java new file mode 100644 index 000000000..7a9804902 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/PlanVelocity.java @@ -0,0 +1,136 @@ +/* + * License is provided in the jar as LICENSE also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/LICENSE + */ +package com.djrapitops.plan; + +import com.djrapitops.plan.api.exceptions.EnableException; +import com.djrapitops.plan.command.PlanVelocityCommand; +import com.djrapitops.plan.system.VelocitySystem; +import com.djrapitops.plan.system.locale.Locale; +import com.djrapitops.plan.system.locale.lang.PluginLang; +import com.djrapitops.plan.system.settings.theme.PlanColorScheme; +import com.djrapitops.plugin.StaticHolder; +import com.djrapitops.plugin.VelocityPlugin; +import com.djrapitops.plugin.api.Benchmark; +import com.djrapitops.plugin.api.utility.log.DebugLog; +import com.djrapitops.plugin.api.utility.log.Log; +import com.djrapitops.plugin.settings.ColorScheme; +import com.google.inject.Inject; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import org.slf4j.Logger; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Path; + +/** + * Velocity Main class. + * + * Based on the PlanBungee class + * + * @author MicleBrick + */ +@Plugin(id = "plan", name = "Plan", version = "4.4.6", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"}) +public class PlanVelocity extends VelocityPlugin implements PlanPlugin { + + private VelocitySystem system; + private Locale locale; + + public static PlanVelocity getInstance() { + return (PlanVelocity) StaticHolder.getInstance(PlanVelocity.class); + } + + @Inject + @DataDirectory + private Path dataFolderPath; + + @Override + public File getDataFolder() { + return dataFolderPath.toFile(); + } + + @Override + public void onEnable() { + super.onEnable(); + try { + system = new VelocitySystem(this); + locale = system.getLocaleSystem().getLocale(); + system.enable(); + + Log.info(locale.getString(PluginLang.ENABLED)); + } catch (AbstractMethodError e) { + Log.error("Plugin ran into AbstractMethodError - Server restart is required. Likely cause is updating the jar without a restart."); + } catch (EnableException e) { + Log.error("----------------------------------------"); + Log.error("Error: " + e.getMessage()); + Log.error("----------------------------------------"); + Log.error("Plugin Failed to Initialize Correctly. If this issue is caused by config settings you can use /planvelocity reload"); + onDisable(); + } catch (Exception e) { + getLogger().error(this.getClass().getSimpleName() + "-v" + getVersion(), e); + Log.error("Plugin Failed to Initialize Correctly. If this issue is caused by config settings you can use /planvelocity reload"); + Log.error("This error should be reported at https://github.com/Rsl1122/Plan-PlayerAnalytics/issues"); + onDisable(); + } + registerCommand("planvelocity", new PlanVelocityCommand(this)); + } + + @Override + public void onDisable() { + system.disable(); + + Log.info(locale.getString(PluginLang.DISABLED)); + Benchmark.pluginDisabled(PlanVelocity.class); + DebugLog.pluginDisabled(PlanVelocity.class); + } + + @Override + public String getVersion() { + return getClass().getAnnotation(Plugin.class).version(); + } + + + @Override + public void onReload() { + // Nothing to be done, systems are disabled + } + + @Override + public InputStream getResource(String resource) { + return getClass().getResourceAsStream(resource); + } + + @Override + public ColorScheme getColorScheme() { + return PlanColorScheme.create(); + } + + @Override + public VelocitySystem getSystem() { + return system; + } + + @Override + public boolean isReloading() { + return reloading; + } + + @Inject + private ProxyServer proxy; + + @Override + public ProxyServer getProxy() { + return proxy; + } + + @Inject + private Logger logger; + + @Override + protected Logger getLogger() { + return logger; + } +} diff --git a/Plan/src/main/java/com/djrapitops/plan/api/VelocityAPI.java b/Plan/src/main/java/com/djrapitops/plan/api/VelocityAPI.java new file mode 100644 index 000000000..ef6b63c30 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/api/VelocityAPI.java @@ -0,0 +1,42 @@ +/* + * License is provided in the jar as LICENSE also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/LICENSE + */ +package com.djrapitops.plan.api; + +import com.djrapitops.plan.data.plugin.PluginData; +import com.djrapitops.plan.system.VelocitySystem; +import com.djrapitops.plan.system.database.databases.operation.FetchOperations; + +import java.util.UUID; + +/** + * PlanAPI extension for Velocity. + * + * Based on BungeeAPI + * + * @author MicleBrick + */ +public class VelocityAPI extends CommonAPI { + + private final VelocitySystem velocitySystem; + + public VelocityAPI(VelocitySystem velocitySystem) { + this.velocitySystem = velocitySystem; + } + + @Override + public void addPluginDataSource(PluginData pluginData) { + velocitySystem.getHookHandler().addPluginDataSource(pluginData); + } + + @Override + public String getPlayerName(UUID uuid) { + return velocitySystem.getCacheSystem().getDataCache().getName(uuid); + } + + @Override + public FetchOperations fetchFromPlanDB() { + return velocitySystem.getDatabaseSystem().getActiveDatabase().fetch(); + } +} diff --git a/Plan/src/main/java/com/djrapitops/plan/command/PlanVelocityCommand.java b/Plan/src/main/java/com/djrapitops/plan/command/PlanVelocityCommand.java new file mode 100644 index 000000000..59f9e123f --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/command/PlanVelocityCommand.java @@ -0,0 +1,57 @@ +package com.djrapitops.plan.command; + +import com.djrapitops.plan.PlanPlugin; +import com.djrapitops.plan.command.commands.*; +import com.djrapitops.plan.command.commands.manage.ManageConDebugCommand; +import com.djrapitops.plan.command.commands.manage.ManageRawDataCommand; +import com.djrapitops.plan.command.commands.manage.ManageUninstalledCommand; +import com.djrapitops.plan.system.locale.Locale; +import com.djrapitops.plan.system.locale.lang.DeepHelpLang; +import com.djrapitops.plan.system.settings.Permissions; +import com.djrapitops.plugin.command.CommandNode; +import com.djrapitops.plugin.command.CommandType; +import com.djrapitops.plugin.command.TreeCmdNode; +import com.djrapitops.plugin.command.defaultcmds.StatusCommand; + +/** + * TreeCommand for the /plan command, and all subcommands. + *

      + * Uses the Abstract Plugin Framework for easier command management. + * + * Based on PlanBungeeCommand + * + * @author MicleBrick + */ +public class PlanVelocityCommand extends TreeCmdNode { + + public PlanVelocityCommand(PlanPlugin plugin) { + super("planvelocity", Permissions.MANAGE.getPermission(), CommandType.CONSOLE, null); + super.setColorScheme(plugin.getColorScheme()); + + Locale locale = plugin.getSystem().getLocaleSystem().getLocale(); + + setInDepthHelp(locale.getArray(DeepHelpLang.PLAN)); + + RegisterCommand registerCommand = new RegisterCommand(plugin); + CommandNode[] analyticsGroup = { + new NetworkCommand(plugin), + new ListServersCommand(plugin), + new ListPlayersCommand(plugin), + }; + CommandNode[] webGroup = { + registerCommand, + new WebUserCommand(plugin, registerCommand, this), + }; + CommandNode[] manageGroup = { + new ManageConDebugCommand(plugin), + new ManageRawDataCommand(plugin), + new BungeeSetupToggleCommand(plugin), // perhaps rename to ProxySetupToggleCommand ? + new ManageUninstalledCommand(plugin), + new ReloadCommand(plugin), + new DisableCommand(plugin), + new StatusCommand<>(plugin, Permissions.MANAGE.getPermission(), plugin.getColorScheme()), +// (Settings.ALLOW_UPDATE.isTrue() ? new UpdateCommand() : null) + }; + setNodeGroups(analyticsGroup, webGroup, manageGroup); + } +} diff --git a/Plan/src/main/java/com/djrapitops/plan/system/BungeeSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/BungeeSystem.java index 0de142b74..5f2dd9e41 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/BungeeSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/BungeeSystem.java @@ -8,11 +8,11 @@ import com.djrapitops.plan.PlanBungee; import com.djrapitops.plan.api.BungeeAPI; import com.djrapitops.plan.api.exceptions.EnableException; import com.djrapitops.plan.data.plugin.HookHandler; -import com.djrapitops.plan.system.cache.BungeeCacheSystem; -import com.djrapitops.plan.system.database.BungeeDBSystem; +import com.djrapitops.plan.system.cache.ProxyCacheSystem; +import com.djrapitops.plan.system.database.ProxyDBSystem; import com.djrapitops.plan.system.database.DBSystem; import com.djrapitops.plan.system.file.FileSystem; -import com.djrapitops.plan.system.info.BungeeInfoSystem; +import com.djrapitops.plan.system.info.ProxyInfoSystem; import com.djrapitops.plan.system.info.server.BungeeServerInfo; import com.djrapitops.plan.system.listeners.BungeeListenerSystem; import com.djrapitops.plan.system.locale.Locale; @@ -42,12 +42,12 @@ public class BungeeSystem extends PlanSystem { versionCheckSystem = new VersionCheckSystem(plugin.getVersion(), localeSupplier); fileSystem = new FileSystem(plugin); configSystem = new BungeeConfigSystem(); - databaseSystem = new BungeeDBSystem(localeSupplier); - cacheSystem = new BungeeCacheSystem(this); + databaseSystem = new ProxyDBSystem(localeSupplier); + cacheSystem = new ProxyCacheSystem(this); listenerSystem = new BungeeListenerSystem(plugin); taskSystem = new BungeeTaskSystem(plugin); - infoSystem = new BungeeInfoSystem(); + infoSystem = new ProxyInfoSystem(); serverInfo = new BungeeServerInfo(plugin); hookHandler = new HookHandler(); diff --git a/Plan/src/main/java/com/djrapitops/plan/system/VelocitySystem.java b/Plan/src/main/java/com/djrapitops/plan/system/VelocitySystem.java new file mode 100644 index 000000000..6d199cb2b --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/VelocitySystem.java @@ -0,0 +1,72 @@ +/* + * License is provided in the jar as LICENSE also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/LICENSE + */ +package com.djrapitops.plan.system; + +import com.djrapitops.plan.PlanVelocity; +import com.djrapitops.plan.api.VelocityAPI; +import com.djrapitops.plan.api.exceptions.EnableException; +import com.djrapitops.plan.data.plugin.HookHandler; +import com.djrapitops.plan.system.cache.ProxyCacheSystem; +import com.djrapitops.plan.system.database.DBSystem; +import com.djrapitops.plan.system.database.ProxyDBSystem; +import com.djrapitops.plan.system.file.FileSystem; +import com.djrapitops.plan.system.info.ProxyInfoSystem; +import com.djrapitops.plan.system.info.server.VelocityServerInfo; +import com.djrapitops.plan.system.listeners.VelocityListenerSystem; +import com.djrapitops.plan.system.locale.Locale; +import com.djrapitops.plan.system.settings.PlanErrorManager; +import com.djrapitops.plan.system.settings.config.BungeeConfigSystem; +import com.djrapitops.plan.system.settings.network.NetworkSettings; +import com.djrapitops.plan.system.tasks.VelocityTaskSystem; +import com.djrapitops.plan.system.update.VersionCheckSystem; +import com.djrapitops.plugin.api.utility.log.Log; + +import java.util.function.Supplier; + +/** + * Represents PlanSystem for PlanVelocity. + * + * Based on BungeeSystem + * + * @author MicleBrick + */ +public class VelocitySystem extends PlanSystem { + + public VelocitySystem(PlanVelocity plugin) { + setTestSystem(this); + + Log.setErrorManager(new PlanErrorManager()); + + Supplier localeSupplier = () -> getLocaleSystem().getLocale(); + + versionCheckSystem = new VersionCheckSystem(plugin.getVersion(), localeSupplier); + fileSystem = new FileSystem(plugin); + configSystem = new BungeeConfigSystem(); // not sure if this needs to be different for velocity + databaseSystem = new ProxyDBSystem(localeSupplier); + cacheSystem = new ProxyCacheSystem(this); + listenerSystem = new VelocityListenerSystem(plugin); + taskSystem = new VelocityTaskSystem(plugin); + + infoSystem = new ProxyInfoSystem(); + serverInfo = new VelocityServerInfo(plugin); + + hookHandler = new HookHandler(); + planAPI = new VelocityAPI(this); + } + + public static VelocitySystem getInstance() { + return PlanVelocity.getInstance().getSystem(); + } + + public void setDatabaseSystem(DBSystem dbSystem) { + this.databaseSystem = dbSystem; + } + + @Override + public void enable() throws EnableException { + super.enable(); + NetworkSettings.placeSettingsToDB(); + } +} diff --git a/Plan/src/main/java/com/djrapitops/plan/system/cache/BungeeCacheSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/cache/BungeeCacheSystem.java deleted file mode 100644 index cdb559730..000000000 --- a/Plan/src/main/java/com/djrapitops/plan/system/cache/BungeeCacheSystem.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.djrapitops.plan.system.cache; - -import com.djrapitops.plan.system.PlanSystem; - -/** - * CacheSystem for Bungee. - *

      - * Used for overriding {@link DataCache} with {@link BungeeDataCache} - * - * @author Rsl1122 - */ -public class BungeeCacheSystem extends CacheSystem { - - public BungeeCacheSystem(PlanSystem system) { - super(new BungeeDataCache(system), system); - } - -} diff --git a/Plan/src/main/java/com/djrapitops/plan/system/cache/ProxyCacheSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/cache/ProxyCacheSystem.java new file mode 100644 index 000000000..e26b44cf9 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/cache/ProxyCacheSystem.java @@ -0,0 +1,18 @@ +package com.djrapitops.plan.system.cache; + +import com.djrapitops.plan.system.PlanSystem; + +/** + * CacheSystem for proxy servers. + *

      + * Used for overriding {@link DataCache} with {@link ProxyDataCache} + * + * @author Rsl1122 + */ +public class ProxyCacheSystem extends CacheSystem { + + public ProxyCacheSystem(PlanSystem system) { + super(new ProxyDataCache(system), system); + } + +} diff --git a/Plan/src/main/java/com/djrapitops/plan/system/cache/BungeeDataCache.java b/Plan/src/main/java/com/djrapitops/plan/system/cache/ProxyDataCache.java similarity index 63% rename from Plan/src/main/java/com/djrapitops/plan/system/cache/BungeeDataCache.java rename to Plan/src/main/java/com/djrapitops/plan/system/cache/ProxyDataCache.java index 56eaea59e..2df8f5203 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/cache/BungeeDataCache.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/cache/ProxyDataCache.java @@ -5,21 +5,21 @@ import com.djrapitops.plan.system.PlanSystem; import java.util.UUID; /** - * Bungee specific DataCache. + * Proxy server specific DataCache. *

      * Used for overriding {@link SessionCache#endSession(UUID, long)}. * * @author Rsl1122 */ -public class BungeeDataCache extends DataCache { +public class ProxyDataCache extends DataCache { - public BungeeDataCache(PlanSystem system) { + public ProxyDataCache(PlanSystem system) { super(system); } @Override public void endSession(UUID uuid, long time) { removeSessionFromCache(uuid); - /* Bungee should not save sessions so session is not removed.. */ + /* Proxy should not save sessions so session is not removed.. */ } } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/database/BungeeDBSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/database/ProxyDBSystem.java similarity index 87% rename from Plan/src/main/java/com/djrapitops/plan/system/database/BungeeDBSystem.java rename to Plan/src/main/java/com/djrapitops/plan/system/database/ProxyDBSystem.java index 2cc2534b3..41a2a2252 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/database/BungeeDBSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/database/ProxyDBSystem.java @@ -15,9 +15,9 @@ import java.util.function.Supplier; * * @author Rsl1122 */ -public class BungeeDBSystem extends DBSystem { +public class ProxyDBSystem extends DBSystem { - public BungeeDBSystem(Supplier locale) { + public ProxyDBSystem(Supplier locale) { super(locale); } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/info/BungeeInfoSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/info/ProxyInfoSystem.java similarity index 91% rename from Plan/src/main/java/com/djrapitops/plan/system/info/BungeeInfoSystem.java rename to Plan/src/main/java/com/djrapitops/plan/system/info/ProxyInfoSystem.java index 527597f46..872ff6bb5 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/info/BungeeInfoSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/info/ProxyInfoSystem.java @@ -7,7 +7,7 @@ package com.djrapitops.plan.system.info; import com.djrapitops.plan.api.exceptions.ParseException; import com.djrapitops.plan.api.exceptions.connection.NoServersException; import com.djrapitops.plan.api.exceptions.connection.WebException; -import com.djrapitops.plan.system.info.connection.BungeeConnectionSystem; +import com.djrapitops.plan.system.info.connection.ProxyConnectionSystem; import com.djrapitops.plan.system.info.request.CacheRequest; import com.djrapitops.plan.system.info.request.GenerateInspectPageRequest; import com.djrapitops.plan.system.info.request.GenerateInspectPluginsTabRequest; @@ -23,10 +23,10 @@ import com.djrapitops.plan.system.webserver.response.pages.NetworkPageResponse; * * @author Rsl1122 */ -public class BungeeInfoSystem extends InfoSystem { +public class ProxyInfoSystem extends InfoSystem { - public BungeeInfoSystem() { - super(new BungeeConnectionSystem()); + public ProxyInfoSystem() { + super(new ProxyConnectionSystem()); } @Override diff --git a/Plan/src/main/java/com/djrapitops/plan/system/info/connection/BungeeConnectionSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/info/connection/ProxyConnectionSystem.java similarity index 95% rename from Plan/src/main/java/com/djrapitops/plan/system/info/connection/BungeeConnectionSystem.java rename to Plan/src/main/java/com/djrapitops/plan/system/info/connection/ProxyConnectionSystem.java index ae9516525..4b09ccafb 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/info/connection/BungeeConnectionSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/info/connection/ProxyConnectionSystem.java @@ -18,15 +18,15 @@ import com.djrapitops.plugin.api.utility.log.Log; import java.util.UUID; /** - * ConnectionSystem for Bungee. + * ConnectionSystem for proxy servers. * * @author Rsl1122 */ -public class BungeeConnectionSystem extends ConnectionSystem { +public class ProxyConnectionSystem extends ConnectionSystem { private long latestServerMapRefresh; - public BungeeConnectionSystem() { + public ProxyConnectionSystem() { latestServerMapRefresh = 0; } diff --git a/Plan/src/main/java/com/djrapitops/plan/system/info/server/VelocityServerInfo.java b/Plan/src/main/java/com/djrapitops/plan/system/info/server/VelocityServerInfo.java new file mode 100644 index 000000000..d3bb32488 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/info/server/VelocityServerInfo.java @@ -0,0 +1,88 @@ +/* + * License is provided in the jar as LICENSE also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/LICENSE + */ +package com.djrapitops.plan.system.info.server; + +import com.djrapitops.plan.PlanVelocity; +import com.djrapitops.plan.api.exceptions.EnableException; +import com.djrapitops.plan.api.exceptions.database.DBOpException; +import com.djrapitops.plan.system.database.databases.Database; +import com.djrapitops.plan.system.info.server.properties.VelocityServerProperties; +import com.djrapitops.plan.system.info.server.properties.ServerProperties; +import com.djrapitops.plan.system.webserver.WebServerSystem; +import com.djrapitops.plugin.api.utility.log.Log; + +import java.util.Optional; +import java.util.UUID; + +/** + * Manages Server information on the Velocity instance. + * + * Based on BungeeServerInfo + * + * @author MicleBrick + */ +public class VelocityServerInfo extends ServerInfo { + + public VelocityServerInfo(PlanVelocity plugin) { + super(new VelocityServerProperties(plugin.getProxy())); + } + + @Override + public Server loadServerInfo() throws EnableException { + checkIfDefaultIP(); + + try { + Database db = Database.getActive(); + // doesn't seem like this would need to be different for velocity, perhaps rename to getProxyInformation()? + Optional velocityInfo = db.fetch().getBungeeInformation(); + if (velocityInfo.isPresent()) { + server = velocityInfo.get(); + updateServerInfo(db); + } else { + server = registerVelocityInfo(db); + } + } catch (DBOpException e) { + throw new EnableException("Failed to read Server information from Database."); + } + return server; + } + + private void updateServerInfo(Database db) { + String accessAddress = WebServerSystem.getInstance().getWebServer().getAccessAddress(); + if (!accessAddress.equals(server.getWebAddress())) { + server.setWebAddress(accessAddress); + db.save().serverInfoForThisServer(server); + } + } + + private void checkIfDefaultIP() throws EnableException { + String ip = ServerInfo.getServerProperties().getIp(); + if ("0.0.0.0".equals(ip)) { + Log.error("IP setting still 0.0.0.0 - Configure AlternativeIP/IP that connects to the Proxy server."); + Log.info("Player Analytics partially enabled (Use /planvelocity to reload config)"); + throw new EnableException("IP setting still 0.0.0.0 - Configure AlternativeIP/IP that connects to the Proxy server."); + } + } + + private Server registerVelocityInfo(Database db) throws EnableException { + ServerProperties properties = ServerInfo.getServerProperties(); + UUID serverUUID = generateNewUUID(properties); + String accessAddress = WebServerSystem.getInstance().getWebServer().getAccessAddress(); + + Server velocityCord = new Server(-1, serverUUID, "VelocityCord", accessAddress, properties.getMaxPlayers()); + db.save().serverInfoForThisServer(velocityCord); + + Optional velocityInfo = db.fetch().getBungeeInformation(); + if (velocityInfo.isPresent()) { + return velocityInfo.get(); + } + throw new EnableException("VelocityCord registration failed (DB)"); + } + + private UUID generateNewUUID(ServerProperties properties) { + String seed = properties.getName() + properties.getIp() + properties.getPort() + properties.getVersion() + properties.getImplVersion(); + return UUID.nameUUIDFromBytes(seed.getBytes()); + } +} diff --git a/Plan/src/main/java/com/djrapitops/plan/system/info/server/properties/VelocityServerProperties.java b/Plan/src/main/java/com/djrapitops/plan/system/info/server/properties/VelocityServerProperties.java new file mode 100644 index 000000000..01a76c09d --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/info/server/properties/VelocityServerProperties.java @@ -0,0 +1,28 @@ +package com.djrapitops.plan.system.info.server.properties; + +import com.djrapitops.plan.system.settings.Settings; +import com.velocitypowered.api.proxy.ProxyServer; + +/** + * ServerProperties for Velocity. + *

      + * Supports RedisBungee for Players online getting. + * + * @author Rsl1122 + */ +public class VelocityServerProperties extends ServerProperties { + + public VelocityServerProperties(ProxyServer server) { + super( + server.getAllServers().toString(), + "Velocity", + server.getBoundAddress().getPort(), + // not sure how to get these + server.getClass().getPackage().getImplementationVersion(), + server.getClass().getPackage().getImplementationVersion(), + Settings.BUNGEE_IP::toString, + -1, // not sure how to get this + RedisCheck.isClassAvailable() ? new RedisPlayersOnlineSupplier() : server::getPlayerCount + ); + } +} \ No newline at end of file diff --git a/Plan/src/main/java/com/djrapitops/plan/system/listeners/VelocityListenerSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/listeners/VelocityListenerSystem.java new file mode 100644 index 000000000..7d61fd098 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/listeners/VelocityListenerSystem.java @@ -0,0 +1,23 @@ +package com.djrapitops.plan.system.listeners; + +import com.djrapitops.plan.PlanVelocity; +import com.djrapitops.plan.system.listeners.velocity.PlayerOnlineListener; + +public class VelocityListenerSystem extends ListenerSystem { + + private final PlanVelocity plugin; + + public VelocityListenerSystem(PlanVelocity plugin) { + this.plugin = plugin; + } + + @Override + protected void registerListeners() { + plugin.registerListener(new PlayerOnlineListener()); + } + + @Override + protected void unregisterListeners() { + plugin.getProxy().getEventManager().unregisterListeners(plugin); + } +} diff --git a/Plan/src/main/java/com/djrapitops/plan/system/listeners/velocity/PlayerOnlineListener.java b/Plan/src/main/java/com/djrapitops/plan/system/listeners/velocity/PlayerOnlineListener.java new file mode 100644 index 000000000..e190c8d81 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/listeners/velocity/PlayerOnlineListener.java @@ -0,0 +1,85 @@ +/* + * License is provided in the jar as LICENSE also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/LICENSE + */ +package com.djrapitops.plan.system.listeners.velocity; + +import com.djrapitops.plan.data.container.Session; +import com.djrapitops.plan.system.cache.SessionCache; +import com.djrapitops.plan.system.info.server.ServerInfo; +import com.djrapitops.plan.system.processing.Processing; +import com.djrapitops.plan.system.processing.processors.info.PlayerPageUpdateProcessor; +import com.djrapitops.plan.system.processing.processors.player.BungeePlayerRegisterProcessor; +import com.djrapitops.plan.system.processing.processors.player.IPUpdateProcessor; +import com.djrapitops.plan.system.webserver.cache.PageId; +import com.djrapitops.plan.system.webserver.cache.ResponseCache; +import com.djrapitops.plugin.api.utility.log.Log; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.Player; + +import java.net.InetAddress; +import java.util.UUID; + +/** + * Player Join listener for Velocity. + * + * Based on the bungee version. + * + * @author MicleBrick + */ +public class PlayerOnlineListener { + + @Subscribe + public void onPostLogin(PostLoginEvent event) { + try { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + String name = player.getUsername(); + InetAddress address = player.getRemoteAddress().getAddress(); + long now = System.currentTimeMillis(); + + SessionCache.getInstance().cacheSession(uuid, new Session(uuid, now, "", "")); + + // maybe rename to ProxyPlayerRegisterProcessor? + Processing.submit(new BungeePlayerRegisterProcessor(uuid, name, now, + new IPUpdateProcessor(uuid, address, now)) + ); + Processing.submit(new PlayerPageUpdateProcessor(uuid)); + ResponseCache.clearResponse(PageId.SERVER.of(ServerInfo.getServerUUID())); + } catch (Exception e) { + Log.toLog(this.getClass(), e); + } + } + + @Subscribe + public void onLogout(DisconnectEvent event) { + try { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + + SessionCache.getInstance().endSession(uuid, System.currentTimeMillis()); + Processing.submit(new PlayerPageUpdateProcessor(uuid)); + ResponseCache.clearResponse(PageId.SERVER.of(ServerInfo.getServerUUID())); + } catch (Exception e) { + Log.toLog(this.getClass(), e); + } + } + + @Subscribe + public void onServerSwitch(ServerConnectedEvent event) { + try { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + + long now = System.currentTimeMillis(); + // Replaces the current session in the cache. + SessionCache.getInstance().cacheSession(uuid, new Session(uuid, now, "", "")); + Processing.submit(new PlayerPageUpdateProcessor(uuid)); + } catch (Exception e) { + Log.toLog(this.getClass(), e); + } + } +} diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/BungeeTaskSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/BungeeTaskSystem.java index a5cf91ab2..727e308f2 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/BungeeTaskSystem.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/BungeeTaskSystem.java @@ -7,7 +7,7 @@ package com.djrapitops.plan.system.tasks; import com.djrapitops.plan.PlanBungee; import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.system.tasks.bungee.BungeeTPSCountTimer; -import com.djrapitops.plan.system.tasks.bungee.EnableConnectionTask; +import com.djrapitops.plan.system.tasks.proxy.EnableConnectionTask; import com.djrapitops.plan.system.tasks.server.NetworkPageRefreshTask; import com.djrapitops.plan.system.tasks.server.PingCountTimerBungee; import com.djrapitops.plan.utilities.file.export.HtmlExport; diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/VelocityTaskSystem.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/VelocityTaskSystem.java new file mode 100644 index 000000000..6a1e0da4b --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/VelocityTaskSystem.java @@ -0,0 +1,49 @@ +/* + * License is provided in the jar as LICENSE also here: + * https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/LICENSE + */ +package com.djrapitops.plan.system.tasks; + +import com.djrapitops.plan.PlanVelocity; +import com.djrapitops.plan.system.settings.Settings; +import com.djrapitops.plan.system.tasks.proxy.EnableConnectionTask; +import com.djrapitops.plan.system.tasks.velocity.VelocityTPSCountTimer; +import com.djrapitops.plan.system.tasks.server.NetworkPageRefreshTask; +import com.djrapitops.plan.system.tasks.server.PingCountTimerVelocity; +import com.djrapitops.plan.utilities.file.export.HtmlExport; +import com.djrapitops.plugin.api.TimeAmount; +import com.djrapitops.plugin.task.RunnableFactory; + +/** + * TaskSystem responsible for registering tasks for Velocity. + * + * @author Rsl1122 + */ +public class VelocityTaskSystem extends TaskSystem { + + private final PlanVelocity plugin; + + public VelocityTaskSystem(PlanVelocity plugin) { + super(new VelocityTPSCountTimer(plugin)); + this.plugin = plugin; + } + + @Override + public void enable() { + registerTasks(); + } + + private void registerTasks() { + registerTask(new EnableConnectionTask()).runTaskAsynchronously(); + registerTask(tpsCountTimer).runTaskTimerAsynchronously(1000, TimeAmount.SECOND.ticks()); + registerTask(new NetworkPageRefreshTask()).runTaskTimerAsynchronously(1500, TimeAmount.MINUTE.ticks()); + if (Settings.ANALYSIS_EXPORT.isTrue()) { + registerTask(new HtmlExport(plugin)).runTaskAsynchronously(); + } + PingCountTimerVelocity pingCountTimer = new PingCountTimerVelocity(); + plugin.registerListener(pingCountTimer); + long startDelay = TimeAmount.SECOND.ticks() * (long) Settings.PING_SERVER_ENABLE_DELAY.getNumber(); + RunnableFactory.createNew("PingCountTimer", pingCountTimer) + .runTaskTimer(startDelay, PingCountTimerVelocity.PING_INTERVAL); + } +} diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/bungee/EnableConnectionTask.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/proxy/EnableConnectionTask.java similarity index 93% rename from Plan/src/main/java/com/djrapitops/plan/system/tasks/bungee/EnableConnectionTask.java rename to Plan/src/main/java/com/djrapitops/plan/system/tasks/proxy/EnableConnectionTask.java index 33204938b..6df77a6f6 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/tasks/bungee/EnableConnectionTask.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/proxy/EnableConnectionTask.java @@ -1,4 +1,4 @@ -package com.djrapitops.plan.system.tasks.bungee; +package com.djrapitops.plan.system.tasks.proxy; import com.djrapitops.plan.system.info.connection.ConnectionSystem; import com.djrapitops.plan.system.info.connection.WebExceptionLogger; diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerVelocity.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerVelocity.java new file mode 100644 index 000000000..493940387 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/server/PingCountTimerVelocity.java @@ -0,0 +1,113 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.djrapitops.plan.system.tasks.server; + +import com.djrapitops.plan.PlanVelocity; +import com.djrapitops.plan.data.store.objects.DateObj; +import com.djrapitops.plan.system.processing.Processing; +import com.djrapitops.plan.system.processing.processors.player.PingInsertProcessor; +import com.djrapitops.plan.system.settings.Settings; +import com.djrapitops.plugin.api.TimeAmount; +import com.djrapitops.plugin.task.AbsRunnable; +import com.djrapitops.plugin.task.RunnableFactory; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.Player; + +import java.util.*; + +/** + * Task that handles player ping calculation on Velocity based servers. + * + * Based on PingCountTimerBungee + * + * @author MicleBrick + */ +public class PingCountTimerVelocity extends AbsRunnable { + + //the server is pinging the client every 40 Ticks (2 sec) - so check it then + //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/PlayerConnection.java#L178 + public static final int PING_INTERVAL = 2 * 20; + + private final Map>> playerHistory = new HashMap<>(); + + @Override + public void run() { + List loggedOut = new ArrayList<>(); + long time = System.currentTimeMillis(); + playerHistory.forEach((uuid, history) -> { + Player player = PlanVelocity.getInstance().getProxy().getPlayer(uuid).orElse(null); + if (player != null) { + int ping = getPing(player); + if (ping < -1 || ping > TimeAmount.SECOND.ms() * 8L) { + // Don't accept bad values + return; + } + history.add(new DateObj<>(time, ping)); + if (history.size() >= 30) { + Processing.submit(new PingInsertProcessor(uuid, new ArrayList<>(history))); + history.clear(); + } + } else { + loggedOut.add(uuid); + } + }); + loggedOut.forEach(playerHistory::remove); + } + + public void addPlayer(Player player) { + playerHistory.put(player.getUniqueId(), new ArrayList<>()); + } + + public void removePlayer(Player player) { + playerHistory.remove(player.getUniqueId()); + } + + private int getPing(Player player) { + return (int) player.getPing(); + } + + @Subscribe + public void onPlayerJoin(ServerConnectedEvent joinEvent) { + Player player = joinEvent.getPlayer(); + RunnableFactory.createNew("Add Player to Ping list", new AbsRunnable() { + @Override + public void run() { + if (player.isActive()) { + addPlayer(player); + } + } + }).runTaskLater(TimeAmount.SECOND.ticks() * (long) Settings.PING_PLAYER_LOGIN_DELAY.getNumber()); + } + + @Subscribe + public void onPlayerQuit(DisconnectEvent quitEvent) { + removePlayer(quitEvent.getPlayer()); + } + + public void clear() { + playerHistory.clear(); + } +} diff --git a/Plan/src/main/java/com/djrapitops/plan/system/tasks/velocity/VelocityTPSCountTimer.java b/Plan/src/main/java/com/djrapitops/plan/system/tasks/velocity/VelocityTPSCountTimer.java new file mode 100644 index 000000000..2121fc14f --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/system/tasks/velocity/VelocityTPSCountTimer.java @@ -0,0 +1,31 @@ +package com.djrapitops.plan.system.tasks.velocity; + +import com.djrapitops.plan.PlanVelocity; +import com.djrapitops.plan.data.container.TPS; +import com.djrapitops.plan.data.container.builders.TPSBuilder; +import com.djrapitops.plan.system.info.server.ServerInfo; +import com.djrapitops.plan.system.tasks.TPSCountTimer; + +public class VelocityTPSCountTimer extends TPSCountTimer { + + public VelocityTPSCountTimer(PlanVelocity plugin) { + super(plugin); + } + + @Override + public void addNewTPSEntry(long nanoTime, long now) { + int onlineCount = ServerInfo.getServerProperties().getOnlinePlayers(); + TPS tps = TPSBuilder.get() + .date(now) + .skipTPS() + .playersOnline(onlineCount) + .usedCPU(getCPUUsage()) + .usedMemory(getUsedMemory()) + .entities(-1) + .chunksLoaded(-1) + .toTPS(); + + history.add(tps); + latestPlayersOnline = onlineCount; + } +} diff --git a/Plan/src/main/java/com/djrapitops/plugin/VelocityPlugin.java b/Plan/src/main/java/com/djrapitops/plugin/VelocityPlugin.java new file mode 100644 index 000000000..c69f9edc4 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plugin/VelocityPlugin.java @@ -0,0 +1,101 @@ +package com.djrapitops.plugin; + +import com.djrapitops.plugin.api.Benchmark; +import com.djrapitops.plugin.api.systems.TaskCenter; +import com.djrapitops.plugin.api.utility.Version; +import com.djrapitops.plugin.api.utility.log.DebugLog; +import com.djrapitops.plugin.command.CommandNode; +import com.djrapitops.plugin.command.velocity.VelocityCommand; +import com.velocitypowered.api.proxy.ProxyServer; +import net.kyori.text.TextComponent; +import org.slf4j.Logger; + +import java.io.IOException; + +/** + * (Based on BungeePlugin) + * + * @author MicleBrick + */ +public abstract class VelocityPlugin implements IPlugin { + + protected boolean reloading; + + @Override + public void onEnable() { + StaticHolder.register(this); + } + + @Override + public void onDisable() { + Class pluginClass = getClass(); + StaticHolder.unRegister(pluginClass); + Benchmark.pluginDisabled(pluginClass); + DebugLog.pluginDisabled(pluginClass); + TaskCenter.cancelAllKnownTasks(pluginClass); + } + + @Override + public void reloadPlugin(boolean full) { + PluginCommon.reload(this, full); + } + + @Override + public void log(String level, String s) { + Logger logger = getLogger(); + switch (level.toUpperCase()) { + case "INFO": + case "I": + logger.info(s); + break; + case "INFO_COLOR": + getProxy().getConsoleCommandSource().sendMessage(TextComponent.of(s)); + break; + case "W": + case "WARN": + case "WARNING": + logger.warn(s); + break; + case "E": + case "ERR": + case "ERROR": + case "SEVERE": + logger.error(s); + break; + default: + logger.info(s); + break; + } + } + + public void registerListener(Object... listeners) { + for (Object listener : listeners) { + getProxy().getEventManager().register(this, listener); + StaticHolder.saveInstance(listener.getClass(), getClass()); + } + } + + @Override + public void registerCommand(String name, CommandNode command) { + getProxy().getCommandManager().register(new VelocityCommand(command), name); + PluginCommon.saveCommandInstances(command, this.getClass()); + } + + protected boolean isNewVersionAvailable(String versionStringUrl) throws IOException { + return Version.checkVersion(getVersion(), versionStringUrl); + } + + @Override + public boolean isReloading() { + return reloading; + } + + @Override + public void setReloading(boolean reloading) { + this.reloading = reloading; + } + + protected abstract ProxyServer getProxy(); + + protected abstract Logger getLogger(); +} diff --git a/Plan/src/main/java/com/djrapitops/plugin/command/velocity/VelocityCMDSender.java b/Plan/src/main/java/com/djrapitops/plugin/command/velocity/VelocityCMDSender.java new file mode 100644 index 000000000..d0013f379 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plugin/command/velocity/VelocityCMDSender.java @@ -0,0 +1,79 @@ +package com.djrapitops.plugin.command.velocity; + +import com.djrapitops.plugin.command.ISender; +import com.djrapitops.plugin.command.SenderType; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import net.kyori.text.TextComponent; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.format.TextDecoration; + +/** + * Class that wraps velocity's CommandSource into an ISender. + * + * @author Rsl1122 + * @since 2.0.0 + */ +public class VelocityCMDSender implements ISender { + + private final CommandSource cs; + + public VelocityCMDSender(CommandSource cs) { + this.cs = cs; + } + + @Override + public String getName() { + if(cs instanceof Player) { + return ((Player) cs).getUsername(); + } + return "Unknown"; + } + + @Override + public void sendMessage(String string) { + cs.sendMessage(TextComponent.of(string)); + } + + @Override + public void sendLink(String pretext, String linkMsg, String url) { + TextComponent message = TextComponent.of(pretext) + .append(TextComponent.of(linkMsg) + .decoration(TextDecoration.UNDERLINE, TextDecoration.State.TRUE)) + .clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)); + cs.sendMessage(message); + } + + @Override + public void sendLink(String linkMsg, String url) { + sendLink("", linkMsg, url); + } + + @Override + public boolean hasPermission(String string) { + return cs.hasPermission(string); + } + + @Override + public void sendMessage(String[] strings) { + for (int i = 1; i < strings.length; i++) { + sendMessage(strings[i]); + } + } + + @Override + public boolean isOp() { + return false; + } + + @Override + public SenderType getSenderType() { + return cs instanceof Player ? SenderType.PLAYER : SenderType.CONSOLE; + } + + @Override + public CommandSource getSender() { + return cs; + } + +} diff --git a/Plan/src/main/java/com/djrapitops/plugin/command/velocity/VelocityCommand.java b/Plan/src/main/java/com/djrapitops/plugin/command/velocity/VelocityCommand.java new file mode 100644 index 000000000..b985bc426 --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plugin/command/velocity/VelocityCommand.java @@ -0,0 +1,27 @@ +package com.djrapitops.plugin.command.velocity; + +import com.djrapitops.plugin.command.CommandNode; +import com.velocitypowered.api.command.Command; +import com.velocitypowered.api.command.CommandSource; + +/** + * Class that is used to wrap a CommandNode implementation into executable + * command by Velocity. + * + * @author Rsl1122 + * @since 2.0.0 + */ +public class VelocityCommand implements Command { + + private final CommandNode commandNode; + + public VelocityCommand(CommandNode commandNode) { + this.commandNode = commandNode; + } + + @Override + public void execute(CommandSource sender, String[] args) { + VelocityCMDSender iSender = new VelocityCMDSender(sender); + commandNode.onCommand(iSender, "", args); + } +} diff --git a/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java b/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java index 7316387ab..a34731f64 100644 --- a/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java +++ b/Plan/src/test/java/com/djrapitops/plan/system/cache/SessionCacheTest.java @@ -48,7 +48,7 @@ public class SessionCacheTest { @Test public void testBungeeReCaching() { - SessionCache cache = new BungeeDataCache(null); + SessionCache cache = new ProxyDataCache(null); cache.cacheSession(uuid, session); Session expected = new Session(uuid, 0, "", ""); cache.cacheSession(uuid, expected);