From 6f4a3bd2aa57a8ae0d413c2d4e9dac2db2ff2399 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 25 Aug 2026 18:12:25 -0600 Subject: [PATCH] Extract VotingPlugin background task --- .../votingplugin/VotingPluginMain.java | 190 +++--------------- .../VotingPluginBackgroundTask.java | 185 +++++++++++++++++ .../VotingPluginBackgroundTaskTest.java | 23 +++ 3 files changed, 233 insertions(+), 165 deletions(-) create mode 100644 VotingPlugin/src/main/java/com/bencodez/votingplugin/backgroundtask/VotingPluginBackgroundTask.java create mode 100644 VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backgroundtask/VotingPluginBackgroundTaskTest.java diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java index 969c3d5027..6f824dc868 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java @@ -1,8 +1,6 @@ package com.bencodez.votingplugin; import java.io.File; -import java.time.Instant; -import java.time.LocalDate; import java.time.LocalDateTime; import java.time.YearMonth; import java.time.ZoneId; @@ -46,15 +44,14 @@ import com.bencodez.advancedcore.api.rewards.injected.RewardInjectValidator; import com.bencodez.advancedcore.api.rewards.injectedrequirement.RequirementInjectConfigurationSection; import com.bencodez.advancedcore.api.user.AdvancedCoreUser; -import com.bencodez.advancedcore.api.user.UserDataFetchMode; import com.bencodez.advancedcore.api.user.UserStartup; import com.bencodez.simpleapi.file.YMLConfig; -import com.bencodez.simpleapi.skull.SkullCache; import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigSpigot; import com.bencodez.simpleapi.time.ParsedDuration; import com.bencodez.simpleapi.updater.Updater; import com.bencodez.votingplugin.broadcast.BroadcastHandler; import com.bencodez.votingplugin.backendproxy.BackendProxyHandler; +import com.bencodez.votingplugin.backgroundtask.VotingPluginBackgroundTask; import com.bencodez.votingplugin.backendproxy.BackendProxyRewardRegistrar; import com.bencodez.votingplugin.broadcast.BroadcastSettings; import com.bencodez.votingplugin.commands.CommandLoader; @@ -212,16 +209,11 @@ public class VotingPluginMain extends AdvancedCorePlugin { @Getter private TopVoterState topVoterState; - @Getter - @Setter - private boolean update = false; @Getter @Setter private Updater updater; - @Getter - private boolean updateStarted = false; @Getter @Setter @@ -254,8 +246,6 @@ public class VotingPluginMain extends AdvancedCorePlugin { @Getter private ServiceSiteHandler serviceSiteHandler; - @Getter - private long lastBackgroundTaskTimeTaken = -1; @Getter @@ -265,12 +255,32 @@ public class VotingPluginMain extends AdvancedCorePlugin { @Getter private DiscordHandler discordHandler; + private VotingPluginBackgroundTask backgroundTask; private VotingPluginVersionInfo versionInfo; private VotingPluginConfigHealth configHealth; private VotifierIntegration votifierIntegration; private VoteLogManager voteLogManager; private VotingPluginWebhookManager webhookManager; + public boolean isUpdate() { + return backgroundTask != null && backgroundTask.isRequested(); + } + + public void setUpdate(boolean update) { + if (backgroundTask == null) { + backgroundTask = new VotingPluginBackgroundTask(this); + } + backgroundTask.setRequested(update); + } + + public boolean isUpdateStarted() { + return backgroundTask != null && backgroundTask.isRunning(); + } + + public long getLastBackgroundTaskTimeTaken() { + return backgroundTask == null ? -1 : backgroundTask.getLastRunTimeSeconds(); + } + public void addDirectlyDefinedRewards(DirectlyDefinedReward directlyDefinedReward) { getRewardHandler().addDirectlyDefined(directlyDefinedReward); } @@ -1030,164 +1040,14 @@ private void setupFiles() { } public synchronized void update() { - if (!(update || configFile.isAlwaysUpdate())) { - return; - } - if (plugin == null) { - return; - } - if (updateStarted) { - return; - } - if (configFile.isUpdateWithPlayersOnlineOnly() && Bukkit.getOnlinePlayers().isEmpty()) { - return; - } - - updateStarted = true; - update = false; - - synchronized (plugin) { - try { - if (plugin == null || !plugin.isEnabled()) { - return; - } - - getUserManager().getDataManager().clearCacheBasic(); - SkullCache.flushWeek(); - - plugin.debug("Starting background task, current cached users: " - + plugin.getUserManager().getDataManager().getUserDataCache().keySet().size()); - - boolean extraBackgroundUpdate = configFile.isExtraBackgroundUpdate(); - long startTime = System.currentTimeMillis(); - - LinkedHashMap> voteToday = new LinkedHashMap<>(); - LinkedHashMap> tempTopVoter = new LinkedHashMap<>(); - - ArrayList topVotersToCheck = new ArrayList<>(); - for (TopVoter top : TopVoter.values()) { - if (plugin == null) { - return; - } - if (plugin.getConfigFile().getLoadTopVoter(top)) { - topVotersToCheck.add(top); - tempTopVoter.put(top, new LinkedHashMap<>()); - } - } - - boolean topVoterIgnorePermissionUse = plugin.getConfigFile().isTopVoterIgnorePermission(); - ArrayList blackList = plugin.getConfigFile().getBlackList(); - - // Compute "today" bounds once (avoid LocalDateTime.now() + MiscUtils - // conversions in tight loops) - final ZoneId zone = ZoneId.systemDefault(); - final LocalDate today = LocalDate.now(zone); - final long startOfDayMs = today.atStartOfDay(zone).toInstant().toEpochMilli(); - final long startOfNextDayMs = today.plusDays(1).atStartOfDay(zone).toInstant().toEpochMilli(); - - final long afterSetup = System.currentTimeMillis(); - - // STREAM all users (MYSQL/SQLITE/FLAT) via the new shortcut method - plugin.getUserManager().forEachUserKeys((uuid, columns) -> { - if (plugin == null || !plugin.isEnabled()) { - return; - } - - VotingPluginUser user = getVotingPluginUserManager().getVotingPluginUser(uuid, false); - user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); - user.updateTempCacheWithColumns(columns); - - try { - if (!user.isBanned() && !blackList.contains(user.getPlayerName())) { - - if (!topVoterIgnorePermissionUse || !user.isTopVoterIgnore()) { - TopVoterPlayer tvp = user.getTopVoterPlayer(); - for (TopVoter top : topVotersToCheck) { - int total = user.getTotal(top); - if (total > 0) { - tempTopVoter.get(top).put(tvp, total); - } - } - } - - // Only allocate if we actually find a "today" vote - HashMap times = null; - - for (Entry entry : user.getLastVotes().entrySet()) { - VoteSite site = entry.getKey(); - if (!site.isEnabled() || site.isHidden()) { - continue; - } - - long time = entry.getValue(); - if (time >= startOfDayMs && time < startOfNextDayMs) { - if (times == null) { - times = new HashMap<>(); - } - times.put(site, LocalDateTime.ofInstant(Instant.ofEpochMilli(time), zone)); - } - } - - if (times != null && !times.isEmpty()) { - voteToday.put(user.getTopVoterPlayer(), times); - } - } - - if (extraBackgroundUpdate && user.isOnline()) { - user.offVote(); - } - - if (!plugin.getPlaceholders().getCacheLevel().onlineOnly() || user.isOnline()) { - plugin.getPlaceholders().onUpdate(user, false); - } - } finally { - user.clearTempCache(); - } - - }, (count) -> { - long time1 = (System.currentTimeMillis() - afterSetup) / 1000; - plugin.debug("Finished getting player data in " + time1 + " seconds, " + count + " users, " - + plugin.getStorageType()); - }); - - // Final processing (runs once after ALL users processed) - topVoterHandler.updateTopVoters(tempTopVoter); - placeholders.onUpdate(); - setVoteToday(voteToday); - serverData.updateValues(); - getSigns().updateSigns(); - - - if (plugin.getConfigFile().isDiscordSRVEnabled() && getDiscordHandler() != null) { - for (TopVoter top : TopVoter.values()) { - if (!plugin.getConfigFile().isDiscordSRVTopVoterNewMessageOnUpdate(top)) { - getDiscordHandler().updateTopVoterMessageId(top); - } - } - } - - plugin.getUserManager().getDataManager().clearNonNeededCachedUsers(); - plugin.extraDebug("Current cached users: " - + plugin.getUserManager().getDataManager().getUserDataCache().keySet().size()); - - update = false; - - long totalTime = (System.currentTimeMillis() - startTime) / 1000; - lastBackgroundTaskTimeTaken = totalTime; - plugin.debug("Background task finished. Total time: " + totalTime + " seconds"); - - } catch (Exception ex) { - if (plugin != null) { - plugin.getLogger().info("Looks like something went wrong"); - } - ex.printStackTrace(); - } finally { - updateStarted = false; - } + if (backgroundTask == null) { + backgroundTask = new VotingPluginBackgroundTask(this); } + backgroundTask.run(); } + @Getter private VoteMilestonesManager voteMilestonesManager; diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backgroundtask/VotingPluginBackgroundTask.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backgroundtask/VotingPluginBackgroundTask.java new file mode 100644 index 0000000000..7437e92d42 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backgroundtask/VotingPluginBackgroundTask.java @@ -0,0 +1,185 @@ +package com.bencodez.votingplugin.backgroundtask; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +import org.bukkit.Bukkit; + +import com.bencodez.advancedcore.api.user.UserDataFetchMode; +import com.bencodez.simpleapi.skull.SkullCache; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.topvoter.TopVoter; +import com.bencodez.votingplugin.topvoter.TopVoterPlayer; +import com.bencodez.votingplugin.user.VotingPluginUser; +import com.bencodez.votingplugin.votesites.VoteSite; + +/** Owns VotingPlugin's periodic background data refresh task and its state. */ +public final class VotingPluginBackgroundTask { + + private final VotingPluginMain plugin; + private volatile boolean requested; + private volatile boolean running; + private volatile long lastRunTimeSeconds = -1; + + public VotingPluginBackgroundTask(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public synchronized void run() { + if (!(requested || plugin.getConfigFile().isAlwaysUpdate())) { + return; + } + if (!plugin.isEnabled() || running) { + return; + } + if (plugin.getConfigFile().isUpdateWithPlayersOnlineOnly() && Bukkit.getOnlinePlayers().isEmpty()) { + return; + } + + running = true; + requested = false; + try { + runRefresh(); + } finally { + running = false; + } + } + + private void runRefresh() { + synchronized (plugin) { + try { + if (!plugin.isEnabled()) { + return; + } + + plugin.getUserManager().getDataManager().clearCacheBasic(); + SkullCache.flushWeek(); + plugin.debug("Starting background task, current cached users: " + + plugin.getUserManager().getDataManager().getUserDataCache().keySet().size()); + + boolean extraBackgroundUpdate = plugin.getConfigFile().isExtraBackgroundUpdate(); + long startTime = System.currentTimeMillis(); + LinkedHashMap> voteToday = new LinkedHashMap<>(); + LinkedHashMap> tempTopVoter = new LinkedHashMap<>(); + + ArrayList topVotersToCheck = new ArrayList<>(); + for (TopVoter top : TopVoter.values()) { + if (plugin.getConfigFile().getLoadTopVoter(top)) { + topVotersToCheck.add(top); + tempTopVoter.put(top, new LinkedHashMap<>()); + } + } + + boolean ignorePermission = plugin.getConfigFile().isTopVoterIgnorePermission(); + ArrayList blackList = plugin.getConfigFile().getBlackList(); + ZoneId zone = ZoneId.systemDefault(); + LocalDate today = LocalDate.now(zone); + long startOfDayMs = today.atStartOfDay(zone).toInstant().toEpochMilli(); + long startOfNextDayMs = today.plusDays(1).atStartOfDay(zone).toInstant().toEpochMilli(); + long afterSetup = System.currentTimeMillis(); + + plugin.getUserManager().forEachUserKeys((uuid, columns) -> { + if (!plugin.isEnabled()) { + return; + } + + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(uuid, false); + user.userDataFetechMode(UserDataFetchMode.TEMP_ONLY); + user.updateTempCacheWithColumns(columns); + try { + if (!user.isBanned() && !blackList.contains(user.getPlayerName())) { + if (!ignorePermission || !user.isTopVoterIgnore()) { + TopVoterPlayer player = user.getTopVoterPlayer(); + for (TopVoter top : topVotersToCheck) { + int total = user.getTotal(top); + if (total > 0) { + tempTopVoter.get(top).put(player, total); + } + } + } + + HashMap times = null; + for (Entry entry : user.getLastVotes().entrySet()) { + VoteSite site = entry.getKey(); + if (!site.isEnabled() || site.isHidden()) { + continue; + } + long time = entry.getValue(); + if (time >= startOfDayMs && time < startOfNextDayMs) { + if (times == null) { + times = new HashMap<>(); + } + times.put(site, LocalDateTime.ofInstant(Instant.ofEpochMilli(time), zone)); + } + } + if (times != null && !times.isEmpty()) { + voteToday.put(user.getTopVoterPlayer(), times); + } + } + + if (extraBackgroundUpdate && user.isOnline()) { + user.offVote(); + } + if (!plugin.getPlaceholders().getCacheLevel().onlineOnly() || user.isOnline()) { + plugin.getPlaceholders().onUpdate(user, false); + } + } finally { + user.clearTempCache(); + } + }, count -> { + long elapsed = (System.currentTimeMillis() - afterSetup) / 1000; + plugin.debug("Finished getting player data in " + elapsed + " seconds, " + count + " users, " + + plugin.getStorageType()); + }); + + plugin.getTopVoterHandler().updateTopVoters(tempTopVoter); + plugin.getPlaceholders().onUpdate(); + plugin.setVoteToday(voteToday); + plugin.getServerData().updateValues(); + plugin.getSigns().updateSigns(); + + if (plugin.getConfigFile().isDiscordSRVEnabled() && plugin.getDiscordHandler() != null) { + for (TopVoter top : TopVoter.values()) { + if (!plugin.getConfigFile().isDiscordSRVTopVoterNewMessageOnUpdate(top)) { + plugin.getDiscordHandler().updateTopVoterMessageId(top); + } + } + } + + plugin.getUserManager().getDataManager().clearNonNeededCachedUsers(); + plugin.extraDebug("Current cached users: " + + plugin.getUserManager().getDataManager().getUserDataCache().keySet().size()); + + lastRunTimeSeconds = (System.currentTimeMillis() - startTime) / 1000; + plugin.debug("Background task finished. Total time: " + lastRunTimeSeconds + " seconds"); + } catch (Exception exception) { + if (plugin.isEnabled()) { + plugin.getLogger().info("Looks like something went wrong"); + } + exception.printStackTrace(); + } + } + } + + public boolean isRequested() { + return requested; + } + + public void setRequested(boolean requested) { + this.requested = requested; + } + + public boolean isRunning() { + return running; + } + + public long getLastRunTimeSeconds() { + return lastRunTimeSeconds; + } +} diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backgroundtask/VotingPluginBackgroundTaskTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backgroundtask/VotingPluginBackgroundTaskTest.java new file mode 100644 index 0000000000..61a63b59fb --- /dev/null +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backgroundtask/VotingPluginBackgroundTaskTest.java @@ -0,0 +1,23 @@ +package com.bencodez.votingplugin.tests.backgroundtask; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import org.junit.jupiter.api.Test; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.backgroundtask.VotingPluginBackgroundTask; + +public class VotingPluginBackgroundTaskTest { + + @Test + public void requestedStateCanBeDelegated() { + VotingPluginBackgroundTask task = new VotingPluginBackgroundTask(mock(VotingPluginMain.class)); + assertFalse(task.isRequested()); + task.setRequested(true); + assertTrue(task.isRequested()); + task.setRequested(false); + assertFalse(task.isRequested()); + } +}