diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java index 875c6a4fec..2f351ef0b1 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java @@ -62,6 +62,7 @@ import com.bencodez.simpleapi.updater.Updater; import com.bencodez.votingplugin.broadcast.BroadcastHandler; import com.bencodez.votingplugin.backendproxy.BackendProxyHandler; +import com.bencodez.votingplugin.backendproxy.BackendProxyRewardRegistrar; import com.bencodez.votingplugin.broadcast.BroadcastSettings; import com.bencodez.votingplugin.commands.CommandLoader; import com.bencodez.votingplugin.commands.executers.CommandAdminVote; @@ -94,15 +95,20 @@ import com.bencodez.votingplugin.signs.Signs; import com.bencodez.votingplugin.specialrewards.NameMCLikeCheckerTask; import com.bencodez.votingplugin.specialrewards.SpecialRewards; +import com.bencodez.votingplugin.specialrewards.SpecialRewardsRewardRegistrar; +import com.bencodez.votingplugin.specialrewards.votemilestones.VoteMilestoneRewardRegistrar; import com.bencodez.votingplugin.specialrewards.votemilestones.VoteMilestonesManager; import com.bencodez.votingplugin.specialrewards.voteparty.VoteParty; +import com.bencodez.votingplugin.specialrewards.voteparty.VotePartyRewardRegistrar; import com.bencodez.votingplugin.specialrewards.votestreak.VoteStreakHandler; +import com.bencodez.votingplugin.specialrewards.votestreak.VoteStreakRewardRegistrar; import com.bencodez.votingplugin.test.VoteTester; import com.bencodez.votingplugin.timequeue.TimeQueueHandler; import com.bencodez.votingplugin.topvoter.TopVoter; import com.bencodez.votingplugin.topvoter.TopVoterHandler; import com.bencodez.votingplugin.topvoter.TopVoterPlayer; import com.bencodez.votingplugin.topvoter.TopVoterState; +import com.bencodez.votingplugin.topvoter.TopVoterRewardRegistrar; import com.bencodez.votingplugin.updater.CheckUpdate; import com.bencodez.votingplugin.user.UserManager; import com.bencodez.votingplugin.user.VotingPluginUser; @@ -113,11 +119,14 @@ import com.bencodez.votingplugin.votelog.listeners.VoteShopPurchaseLoggerListener; import com.bencodez.votingplugin.votereminding.VoteRemindersLegacyMigrator; import com.bencodez.votingplugin.votereminding.VoteRemindersListener; +import com.bencodez.votingplugin.votereminding.VoteReminderRewardRegistrar; import com.bencodez.votingplugin.votereminding.VoteRemindersManager; import com.bencodez.votingplugin.votereminding.store.UserDataVoteReminderCooldownStore; import com.bencodez.votingplugin.voteshop.VoteShopManager; +import com.bencodez.votingplugin.voteshop.VoteShopRewardRegistrar; import com.bencodez.votingplugin.votesites.VoteSite; import com.bencodez.votingplugin.votesites.VoteSiteManager; +import com.bencodez.votingplugin.votesites.VoteSiteRewardRegistrar; import com.bencodez.votingplugin.webhook.VotingPluginWebhooks; import com.bencodez.votingplugin.webhook.WebhookRewardEntry; import com.bencodez.votingplugin.webhook.WebhookRewardParser; @@ -469,729 +478,20 @@ private void loadBungeeHandler() { * * @param rewardPath full configuration path to the rewards section */ - private void addSpecialRewardsEditorPath(String rewardPath) { - addDirectlyDefinedRewards(new DirectlyDefinedReward(rewardPath) { - - @Override - public void setData(String path, Object value) { - plugin.getSpecialRewardsConfig().getData().set(path, value); - } - - @Override - public void createSection(String key) { - plugin.getSpecialRewardsConfig().getData().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return plugin.getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - plugin.getSpecialRewardsConfig().saveData(); - } - }); - } - public void loadDirectlyDefined() { getRewardHandler().getDirectlyDefinedRewards().clear(); - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteReminderOptions.Defaults.Rewards") { - - @Override - public void createSection(String key) { - getConfigFile().saveData(); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigFile().getData(); - } - - @Override - public void save() { - getConfigFile().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigFile().setValue(path, value); - } - }); - - ConfigurationSection sec = getConfigFile().getData().getConfigurationSection("VoteReminders"); - - if (sec != null) { - for (String key : sec.getKeys(false)) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteReminders." + key + ".Rewards") { - - @Override - public void createSection(String key) { - getConfigFile().saveData(); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigFile().getData(); - } - - @Override - public void save() { - getConfigFile().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigFile().setValue(path, value); - } - }); - } - } - - // vote cooldown ended - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteCoolDownEndedReward") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // any site rewards - addDirectlyDefinedRewards(new DirectlyDefinedReward("AnySiteRewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - addDirectlyDefinedRewards(new DirectlyDefinedReward("EverySiteReward") { - - @Override - public void createSection(String key) { - getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigVoteSites().getData(); - } - - @Override - public void save() { - getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigVoteSites().setValue(path, value); - } - }); - - // login rewards - addDirectlyDefinedRewards(new DirectlyDefinedReward("LoginRewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // logout rewards - addDirectlyDefinedRewards(new DirectlyDefinedReward("LogoutRewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // VoteParty - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteParty.Rewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // legacy reward handlng for migrator - if (getSpecialRewardsConfig().getData().getConfigurationSection("Cumulative") != null) { - for (String num : getSpecialRewardsConfig().getData().getConfigurationSection("Cumulative") - .getKeys(false)) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("Cumulative." + num + ".Rewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - } - - if (getSpecialRewardsConfig().getData().getConfigurationSection("Milestones") != null) { - for (String num : getSpecialRewardsConfig().getData().getConfigurationSection("Milestones") - .getKeys(false)) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("Milestones." + num + ".Rewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - } - - // legacy for migrator - addDirectlyDefinedRewards(new DirectlyDefinedReward("AllSites") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // legacy for migrator - addDirectlyDefinedRewards(new DirectlyDefinedReward("AlmostAllSites") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // legacy for migrator - addDirectlyDefinedRewards(new DirectlyDefinedReward("FirstVote") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // legacy for migrator - addDirectlyDefinedRewards(new DirectlyDefinedReward("FirstVoteToday") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - // VoteSites - for (VoteSite site : plugin.getVoteSiteManager().getVoteSites()) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteSites." + site.getKey() + ".Rewards") { - - @Override - public void createSection(String key) { - getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigVoteSites().getData(); - } - - @Override - public void save() { - getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigVoteSites().setValue(path, value); - } - }); - - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteSites." + site.getKey() + ".WaitUntilVoteDelayRewards") { - - @Override - public void createSection(String key) { - getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigVoteSites().getData(); - } - - @Override - public void save() { - getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigVoteSites().setValue(path, value); - } - }); - - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteSites." + site.getKey() + ".CoolDownEndRewards") { - - @Override - public void createSection(String key) { - getConfigVoteSites().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getConfigVoteSites().getData(); - } - - @Override - public void save() { - getConfigVoteSites().saveData(); - } - - @Override - public void setData(String path, Object value) { - getConfigVoteSites().setValue(path, value); - } - }); - } - - ConfigurationSection voteMilestonesSection = plugin.getSpecialRewardsConfig().getData() - .getConfigurationSection("VoteMilestones"); - - if (voteMilestonesSection != null) { - for (String milestoneId : voteMilestonesSection.getKeys(false)) { - addSpecialRewardsEditorPath("VoteMilestones." + milestoneId + ".Rewards"); - } - } - - // VoteStreak rewards - ConfigurationSection voteStreaksSection = plugin.getSpecialRewardsConfig().getData() - .getConfigurationSection("VoteStreaks"); - - if (voteStreaksSection != null) { - for (String streakId : voteStreaksSection.getKeys(false)) { - if ("ProgressGroups".equalsIgnoreCase(streakId)) { - continue; - } - - ConfigurationSection streakSection = voteStreaksSection.getConfigurationSection(streakId); - if (streakSection == null) { - continue; - } - - addSpecialRewardsEditorPath("VoteStreaks." + streakId + ".Rewards"); - } - - ConfigurationSection progressGroupsSection = voteStreaksSection.getConfigurationSection("ProgressGroups"); - - if (progressGroupsSection != null) { - for (String groupId : progressGroupsSection.getKeys(false)) { - ConfigurationSection groupSection = progressGroupsSection.getConfigurationSection(groupId); - if (groupSection == null) { - continue; - } - - addSpecialRewardsEditorPath( - "VoteStreaks.ProgressGroups." + groupId + ".LostRewards"); - - ConfigurationSection milestonesSection = groupSection.getConfigurationSection("Milestones"); - if (milestonesSection == null) { - continue; - } - - for (String milestoneId : milestonesSection.getKeys(false)) { - ConfigurationSection milestoneSection = milestonesSection - .getConfigurationSection(milestoneId); - - if (milestoneSection == null) { - continue; - } - - addSpecialRewardsEditorPath( - "VoteStreaks.ProgressGroups." + groupId - + ".Milestones." + milestoneId + ".Rewards"); - } - } - } - } - - // vote streaks, old way - String[] types = new String[] { "Day", "Week", "Month" }; - for (String type : types) { - for (String str : plugin.getSpecialRewardsConfig().getVoteStreakVotes(type)) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteStreak." + type + "." + str + ".Rewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - } - - for (String path : plugin.getSpecialRewardsConfig().getMonthlyPossibleRewardPlaces()) { - addDirectlyDefinedRewards( - new DirectlyDefinedReward(plugin.getSpecialRewardsConfig().getMonthlyAwardRewardsPath(path)) { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - - for (String path : plugin.getSpecialRewardsConfig().getWeeklyPossibleRewardPlaces()) { - addDirectlyDefinedRewards( - new DirectlyDefinedReward(plugin.getSpecialRewardsConfig().getWeeklyAwardRewardsPath(path)) { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - - for (String path : plugin.getSpecialRewardsConfig().getDailyPossibleRewardPlaces()) { - addDirectlyDefinedRewards( - new DirectlyDefinedReward(plugin.getSpecialRewardsConfig().getDailyAwardRewardsPath(path)) { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - } - - for (String identifier : plugin.getShopFile().getShopIdentifiers()) { - addDirectlyDefinedRewards(new DirectlyDefinedReward("Shop." + identifier + ".Rewards") { - - @Override - public void createSection(String key) { - getShopFile().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getShopFile().getData(); - } - - @Override - public void save() { - getShopFile().saveData(); - } - - @Override - public void setData(String path, Object value) { - getShopFile().setValue(path, value); - } - }); - } - - ConfigurationSection categoriesSection = plugin.getShopFile().getCategoriesSection(); - if (categoriesSection != null) { - for (String categoryShop : categoriesSection.getKeys(false)) { - if (plugin.getShopFile().getCategoryShopSection(categoryShop) != null) { - for (String categoryShopItems : plugin.getShopFile().getCategoryShopSection(categoryShop) - .getKeys(false)) { - addDirectlyDefinedRewards(new DirectlyDefinedReward( - "Categories." + categoryShop + "." + categoryShopItems + ".Rewards") { - - @Override - public void createSection(String key) { - getShopFile().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getShopFile().getData(); - } - - @Override - public void save() { - getShopFile().saveData(); - } - - @Override - public void setData(String path, Object value) { - getShopFile().setValue(path, value); - } - }); - } - } - } - } - - // NameMC like reward - addDirectlyDefinedRewards(new DirectlyDefinedReward("NameMCLikeReward.Rewards") { - - @Override - public void createSection(String key) { - getSpecialRewardsConfig().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getSpecialRewardsConfig().getData(); - } - - @Override - public void save() { - getSpecialRewardsConfig().saveData(); - } - - @Override - public void setData(String path, Object value) { - getSpecialRewardsConfig().setValue(path, value); - } - }); - - addDirectlyDefinedRewards(new DirectlyDefinedReward("BungeeVotePartyRewards") { - - @Override - public void createSection(String key) { - getBungeeSettings().createSection(key); - } - - @Override - public ConfigurationSection getFileData() { - return getBungeeSettings().getData(); - } - - @Override - public void save() { - getBungeeSettings().saveData(); - } - - @Override - public void setData(String path, Object value) { - getBungeeSettings().setValue(path, value); - } - }); + VoteReminderRewardRegistrar.register(this); + SpecialRewardsRewardRegistrar.register(this); + VoteSiteRewardRegistrar.register(this); + VotePartyRewardRegistrar.register(this); + VoteMilestoneRewardRegistrar.register(this); + VoteStreakRewardRegistrar.register(this); + TopVoterRewardRegistrar.register(this); + VoteShopRewardRegistrar.register(this); + BackendProxyRewardRegistrar.register(this); getRewardHandler().checkDirectlyDefined(); - } private void loadTimer() { diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyHandler.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyHandler.java index 00c4d1435a..671b6812d0 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyHandler.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyHandler.java @@ -1,868 +1,184 @@ -// File: com/bencodez/votingplugin/backendproxy/BackendProxyHandler.java package com.bencodez.votingplugin.backendproxy; -import java.io.File; -import java.sql.SQLException; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; import org.bukkit.event.Listener; -import org.eclipse.paho.client.mqttv3.MqttException; -import com.bencodez.advancedcore.api.misc.MiscUtils; -import com.bencodez.advancedcore.api.rewards.RewardBuilder; import com.bencodez.advancedcore.api.time.TimeType; -import com.bencodez.advancedcore.api.user.UserStorage; import com.bencodez.advancedcore.bungeeapi.globaldata.GlobalDataHandler; -import com.bencodez.advancedcore.bungeeapi.globaldata.GlobalMySQL; -import com.bencodez.simpleapi.encryption.EncryptionHandler; import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; -import com.bencodez.simpleapi.servercomm.global.GlobalMessageListener; import com.bencodez.simpleapi.servercomm.mqtt.MqttHandler; -import com.bencodez.simpleapi.servercomm.mqtt.MqttServerComm; import com.bencodez.simpleapi.servercomm.mysql.MySqlMessenger; -import com.bencodez.simpleapi.servercomm.pluginmessage.PluginMessageHandler; import com.bencodez.simpleapi.servercomm.redis.RedisHandler; -import com.bencodez.simpleapi.servercomm.redis.RedisListener; import com.bencodez.simpleapi.servercomm.sockets.ClientHandler; import com.bencodez.simpleapi.servercomm.sockets.SocketHandler; -import com.bencodez.simpleapi.servercomm.sockets.SocketReceiver; import com.bencodez.simpleapi.sql.data.DataValue; -import com.bencodez.simpleapi.sql.data.DataValueBoolean; -import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigSpigot; import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.backendproxy.cache.ProcessedVoteCache; +import com.bencodez.votingplugin.backendproxy.global.BackendGlobalDataSync; +import com.bencodez.votingplugin.backendproxy.messaging.BackendProxyMessageRouter; import com.bencodez.votingplugin.backendproxy.presence.BackendPresenceManager; +import com.bencodez.votingplugin.backendproxy.transport.BackendProxyTransportManager; +import com.bencodez.votingplugin.backendproxy.voteparty.BackendVotePartySync; import com.bencodez.votingplugin.proxy.BungeeMethod; -import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot; -import com.bencodez.votingplugin.proxy.VotingPluginWire; -import com.bencodez.votingplugin.user.VotingPluginUser; -import com.bencodez.votingplugin.util.ServiceSiteValidator; -import com.bencodez.votingplugin.votesites.VoteSite; import lombok.Getter; /** - * Handler for Bungee/proxy server integration. + * Coordinates backend/proxy communication components. */ public class BackendProxyHandler implements Listener { - private static final long PROCESSED_VOTE_TTL_MILLIS = TimeUnit.MINUTES.toMillis(30); + private final VotingPluginMain plugin; + private final ProcessedVoteCache processedVoteCache = new ProcessedVoteCache(); + private final BackendProxyTransportManager transportManager; + private final BackendGlobalDataSync globalDataSync; - @Getter - private final ConcurrentHashMap processedWireVotes = new ConcurrentHashMap<>(); private BackendPresenceManager presenceManager; - - @Getter - private ClientHandler clientHandler; - - private EncryptionHandler encryptionHandler; + private BackendVotePartySync votePartySync; + private BackendProxyMessageRouter messageRouter; @Getter private BungeeMethod method; - - private VotingPluginMain plugin; - - @Getter - private int bungeeVotePartyCurrent = -2; - - @Getter - private int bungeeVotePartyRequired = -2; - - @Getter - private SocketHandler socketHandler; - - private GlobalDataHandler globalDataHandler; - - @Getter - private ScheduledExecutorService timer; - - @Getter - private RedisHandler redisHandler; - @Getter private GlobalMessageHandler globalMessageHandler; - private Thread redisThread; - - @Getter - private MySqlMessenger backendMysqlMessenger; - - @Getter - private MqttHandler mqttHandler; - - /** - * Constructs a new BackendProxyHandler. - * - * @param plugin the main plugin instance - */ public BackendProxyHandler(VotingPluginMain plugin) { this.plugin = plugin; + transportManager = new BackendProxyTransportManager(plugin); + globalDataSync = new BackendGlobalDataSync(plugin, this::sendEnvelope); } /** - * Checks and processes global data from the global data handler. - */ - public void checkGlobalData() { - HashMap data = globalDataHandler.getExact(plugin.getBungeeSettings().getServer()); - - if (data.containsKey("ForceUpdate")) { - boolean b = checkGlobalDataTimeValue(data.get("ForceUpdate")); - if (b) { - if (plugin.getStorageType().equals(UserStorage.MYSQL)) { - plugin.getMysql().clearCacheBasic(); - } - plugin.getUserManager().getDataManager().clearCache(); - plugin.setUpdate(true); - plugin.update(); - globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), "ForceUpdate", false); - } - } - - boolean forceUpdate = false; - - if (checkGlobalDataTime(TimeType.MONTH, data)) { - forceUpdate = true; - } - if (checkGlobalDataTime(TimeType.WEEK, data)) { - forceUpdate = true; - } - if (checkGlobalDataTime(TimeType.DAY, data)) { - forceUpdate = true; - } - - if (forceUpdate) { - HashMap dataToSet = new HashMap<>(); - dataToSet.put("FinishedProcessing", new DataValueBoolean(true)); - dataToSet.put("Processing", new DataValueBoolean(false)); - globalDataHandler.setData(plugin.getBungeeSettings().getServer(), dataToSet); - } - } - - /** - * Checks global data for a time type change. - * - * @param type the time type to check - * @param data the global data map - * @return true if currently processing a time change - */ - public boolean checkGlobalDataTime(TimeType type, HashMap data) { - boolean isProcessing = false; - if (data.containsKey(type.toString())) { - - DataValue value = data.get(type.toString()); - boolean b = checkGlobalDataTimeValue(value); - if (b) { - long lastUpdated = Long.valueOf(data.get("LastUpdated").getString()).longValue(); - plugin.debug("LastUpdated: " + lastUpdated); - if (LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli() - lastUpdated > 1000 * 60 * 60 - * 2) { - plugin.getLogger().warning("Ignoring bungee time change since it was more than 2 hours ago"); - globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false); - return false; - } - - globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), "Processing", true); - isProcessing = true; - - plugin.debug("Detected time change from bungee: " + type.toString()); - plugin.getTimeChecker().forceChanged(type, false, true, true); - globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false); - - HashMap fields = new HashMap<>(); - fields.put("server", plugin.getBungeeSettings().getServer()); - sendSubChannel("TimeChangeFinished", fields); - } - } - return isProcessing; - } - - /** - * Checks and extracts the boolean value from a DataValue. - * - * @param data the data value - * @return the boolean value - */ - public boolean checkGlobalDataTimeValue(DataValue data) { - if (data.isBoolean()) { - return data.getBoolean(); - } - return Boolean.valueOf(data.getString()); - } - - /** - * Closes and cleans up all handlers and connections. - */ - public void close() { - if (presenceManager != null) { - presenceManager.stop(); - } - - if (backendMysqlMessenger != null) { - backendMysqlMessenger.shutdown(); - } - - if (socketHandler != null) { - socketHandler.closeConnection(); - } - if (clientHandler != null) { - clientHandler.stopConnection(); - } - plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent); - plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired); - if (globalDataHandler != null) { - globalDataHandler.getGlobalMysql().close(); - } - } - - /** - * Loads and initializes the bungee handler with the configured method. + * Loads the configured backend/proxy communication components. */ public void load() { plugin.debug("Loading backend proxy handler"); - method = BungeeMethod.getByName(plugin.getBungeeSettings().getBungeeMethod()); - plugin.getLogger().info("Using BungeeMethod: " + method.toString()); - loadGlobalMysql(); - + globalDataSync.load(); globalMessageHandler = new GlobalMessageHandler() { @Override public void sendMessage(JsonEnvelope envelope) { - if (method.equals(BungeeMethod.MYSQL)) { - try { - backendMysqlMessenger.sendToProxy(envelope); - } catch (SQLException e) { - e.printStackTrace(); - } - } else if (method.equals(BungeeMethod.PLUGINMESSAGING)) { - plugin.getPluginMessaging().sendEnvelope(envelope); - } else if (method.equals(BungeeMethod.SOCKETS)) { - sendEnvelopeSocket(envelope); - } else if (method.equals(BungeeMethod.REDIS)) { - redisHandler.publishEnvelope(plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin", - envelope); - } else if (method.equals(BungeeMethod.MQTT)) { - try { - mqttHandler.publishEnvelope( - plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/proxy", envelope); - } catch (Exception e) { - e.printStackTrace(); - } - } + transportManager.send(envelope); } }; presenceManager = new BackendPresenceManager(plugin, method, globalMessageHandler); - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE) { - @Override - public void onReceive(JsonEnvelope msg) { - handleWireVote(msg); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_ONLINE) { - @Override - public void onReceive(JsonEnvelope msg) { - handleWireVote(msg); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_DELAY_REJECTED) { - @Override - public void onReceive(JsonEnvelope msg) { - handleWireVoteDelayRejected(msg); - } - }); - - if (method.supportsBackendPresence()) { - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_PRESENCE_RESYNC_REQUEST) { - @Override - public void onReceive(JsonEnvelope msg) { - presenceManager.handleResyncRequest(msg); - } - }); - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_PRESENCE_SNAPSHOT_REQUEST) { - @Override - public void onReceive(JsonEnvelope msg) { - presenceManager.handleSnapshotRequest(msg); - } - }); - } - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_UPDATE) { - @Override - public void onReceive(JsonEnvelope msg) { - VotingPluginWire.VoteUpdate v = VotingPluginWire.readVoteUpdate(msg); - - String playerUuid = v.uuid; - if (playerUuid == null || playerUuid.isEmpty()) { - return; - } - - plugin.debug("pluginmessaging voteupdate received for " + playerUuid + ": " + v.votePartyCurrent + "/" - + v.votePartyRequired + " on " + v.service); - - if (v.votePartyCurrent >= 0 || bungeeVotePartyCurrent == -2) { - bungeeVotePartyCurrent = v.votePartyCurrent; - } - if (v.votePartyRequired >= 0 || bungeeVotePartyRequired == -2) { - bungeeVotePartyRequired = v.votePartyRequired; - } - plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent); - plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired); - - VotingPluginUser user = plugin.getVotingPluginUserManager() - .getVotingPluginUser(UUID.fromString(playerUuid)); - user.cache(); - user.offVote(); - - String service = v.service; - long time = v.time; - - if (service != null && !service.isEmpty() && time > 0) { - user.setTime(plugin.getVoteSiteManager().getVoteSite(service, true), time); - } else if (service != null && !service.isEmpty() && time <= 0 - && plugin.getBungeeSettings().isBungeeDebug()) { - plugin.debug("Invalid last vote time received from bungee: " + time); - } - - plugin.setUpdate(true); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_BUNGEE_TIME_CHANGE) { - @Override - public void onReceive(JsonEnvelope msg) { - checkGlobalData(); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_BROADCAST) { - @Override - public void onReceive(JsonEnvelope msg) { - Map f = msg.getFields(); - - final String uuidStr = nvl(f.get(VotingPluginWire.K_UUID)); - final String playerNameRaw = nvl(f.get(VotingPluginWire.K_PLAYER)); - final String service = nvl(f.get(VotingPluginWire.K_SERVICE)); - - if (uuidStr.isEmpty() || service.isEmpty()) { - return; - } - - UUID javaUuid; - try { - javaUuid = UUID.fromString(uuidStr); - } catch (Exception e) { - plugin.getLogger().warning("Invalid UUID in VoteBroadcast: " + uuidStr); - return; - } - - @SuppressWarnings("unused") - final long time = readLongSafe(f.get(VotingPluginWire.K_TIME), 0L); - final String totalsRaw = nvl(f.get(VotingPluginWire.K_TOTALS)); - final VoteTotalsSnapshot totals = totalsRaw.isEmpty() ? null : VoteTotalsSnapshot.parseStorage(totalsRaw); - - VoteSite voteSite = plugin.getVoteSiteManager() - .getVoteSite(plugin.getVoteSiteManager().getVoteSiteName(true, service), true); - - if (voteSite == null) { - plugin.getLogger().warning("No voting site with the service site: '" + service + "'"); - return; - } - if (!voteSite.isEnabled()) { - plugin.debug("Votesite: " + voteSite.getKey() + " is not enabled (VoteBroadcast)"); - return; - } - - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid, playerNameRaw); - user.cache(); - user.updateName(true); - - if (plugin.getBroadcastHandler() == null) { - return; - } - - if (user.isVanished()) { - plugin.debug("Not broadcasting vote for vanished user: " + user.getPlayerName()); - return; - } - - final boolean online = f.containsKey(VotingPluginWire.K_WAS_ONLINE) - ? Boolean.parseBoolean(f.get(VotingPluginWire.K_WAS_ONLINE)) - : user.isOnline(); - plugin.getBroadcastHandler().broadcastVote(user.getJavaUUID(), user.getPlayerName(), - voteSite.getDisplayName(), online, totals); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener("Status") { - @Override - public void onReceive(JsonEnvelope msg) { - String server = nvl(msg.getFields().get("server")); - HashMap out = new HashMap<>(); - out.put("server", server); - sendSubChannel("statusokay", out); - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener("ServerName") { - @Override - public void onReceive(JsonEnvelope msg) { - String server = nvl(msg.getFields().get("server")); - if (!plugin.getOptions().getServer().equals(server)) { - plugin.getLogger().warning("Server name doesn't match in BungeeSettings.yml, should be " + server); - } - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener("VotePartyBungee") { - @Override - public void onReceive(JsonEnvelope msg) { - for (final String cmd : plugin.getBungeeSettings().getBungeeVotePartyGlobalCommands()) { - plugin.getBukkitScheduler().runTask(plugin, new Runnable() { - @Override - public void run() { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), cmd); - } - }); - } - for (Player p : Bukkit.getOnlinePlayers()) { - new RewardBuilder(plugin.getBungeeSettings().getData(), "BungeeVotePartyRewards").send(p); - } - } - }); - - globalMessageHandler.addListener(new GlobalMessageListener("VotePartyBroadcast") { - @Override - public void onReceive(JsonEnvelope msg) { - String broadcast = nvl(msg.getFields().get("broadcast")); - MiscUtils.getInstance().broadcast(broadcast); - } - }); - - if (method.equals(BungeeMethod.MYSQL)) { - plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel()); - - try { - backendMysqlMessenger = new MySqlMessenger("VotingPlugin", - plugin.getMysql().getMysql().getConnectionManager().getDataSource(), - MySqlMessenger.Mode.BACKEND, plugin.getOptions().getServer(), msg -> { - if (plugin.getBungeeSettings().isBungeeDebug()) { - plugin.debug("Proxy sent envelope: " + msg.envelope.getSubChannel() + " " - + msg.envelope.getFields()); - } - globalMessageHandler.onMessage(msg.envelope); - }); - } catch (SQLException e) { - e.printStackTrace(); - } - } else if (method.equals(BungeeMethod.REDIS)) { - redisHandler = new RedisHandler(plugin.getBungeeSettings().getRedisHost(), - plugin.getBungeeSettings().getRedisPort(), plugin.getBungeeSettings().getRedisUsername(), - plugin.getBungeeSettings().getRedisPassword(), plugin.getBungeeSettings().getRedisdbindex()) { - - @Override - public void debug(String message) { - if (plugin.getBungeeSettings().isBungeeDebug()) { - plugin.debug(message); - } - } - }; - - redisThread = new Thread(new Runnable() { - @Override - public void run() { - if (plugin.isEnabled()) { - RedisListener listener = redisHandler.createEnvelopeListener( - plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin_" - + plugin.getBungeeSettings().getServer(), - (ch, env) -> globalMessageHandler.onMessage(env)); - redisHandler.loadListener(listener); - } - } - }); - redisThread.start(); - - } else if (method.equals(BungeeMethod.PLUGINMESSAGING)) { - plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel()); - - if (plugin.getBungeeSettings().isPluginMessageEncryption()) { - encryptionHandler = new EncryptionHandler(plugin.getName(), new File(plugin.getDataFolder(), "secretkey.key")); - plugin.getPluginMessaging().setEncryptionHandler(encryptionHandler); - } - - plugin.getPluginMessaging().setDebug(plugin.getBungeeSettings().isBungeeDebug()); - - plugin.getPluginMessaging().add(new PluginMessageHandler() { - @Override - public void onReceive(JsonEnvelope envelope) { - globalMessageHandler.onMessage(envelope); - } - }); - - } else if (method.equals(BungeeMethod.SOCKETS)) { - encryptionHandler = new EncryptionHandler(plugin.getName(), new File(plugin.getDataFolder(), "secretkey.key")); - - clientHandler = new ClientHandler(plugin.getBungeeSettings().getBungeeServerHost(), - plugin.getBungeeSettings().getBungeeServerPort(), encryptionHandler, - plugin.getBungeeSettings().isBungeeDebug()); - - socketHandler = new SocketHandler("vp-socket", plugin.getBungeeSettings().getSpigotServerHost(), - plugin.getBungeeSettings().getSpigotServerPort(), encryptionHandler, - plugin.getBungeeSettings().isBungeeDebug()) { - - @Override - public void log(String str) { - plugin.getLogger().info(str); - } - }; - - socketHandler.add(new SocketReceiver() { - @Override - public void onReceiveEnvelope(JsonEnvelope envelope) { - globalMessageHandler.onMessage(envelope); - } - }); - - } else if (method.equals(BungeeMethod.MQTT)) { - try { - String id = plugin.getBungeeSettings().getMqttClientID(); - if (id.isEmpty()) { - id = plugin.getOptions().getServer(); - } - mqttHandler = new MqttHandler(new MqttServerComm(id, plugin.getBungeeSettings().getMqttBrokerURL(), - plugin.getBungeeSettings().getMqttUsername(), plugin.getBungeeSettings().getMqttPassword()), 2); - - mqttHandler.subscribeEnvelopes(plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/" - + plugin.getOptions().getServer(), (topic, envelope) -> globalMessageHandler.onMessage(envelope)); - - } catch (MqttException e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - bungeeVotePartyCurrent = plugin.getServerData().getBungeeVotePartyCurrent(); - bungeeVotePartyRequired = plugin.getServerData().getBungeeVotePartyRequired(); + votePartySync = new BackendVotePartySync(plugin); + messageRouter = new BackendProxyMessageRouter(plugin, presenceManager, globalDataSync, votePartySync, + processedVoteCache); + messageRouter.register(globalMessageHandler, method); + transportManager.start(method, globalMessageHandler); if (plugin.getOptions().getServer().equalsIgnoreCase("pleaseset")) { plugin.getLogger().warning("Server name for bungee voting is not set, please set it"); } - presenceManager.start(); } /** - * Announces a player login to the configured proxy transport. + * Closes backend/proxy components and persists cached proxy state. */ + public void close() { + if (presenceManager != null) { + presenceManager.stop(); + } + transportManager.close(); + if (votePartySync != null) { + votePartySync.persist(); + } + globalDataSync.close(); + } + public void playerOnline(String playerName, String uuid) { - presenceManager.playerOnline(playerName, uuid); + if (presenceManager != null) { + presenceManager.playerOnline(playerName, uuid); + } } - /** - * Announces a player disconnect when backend presence is supported. - */ public void playerOffline(String playerName) { - presenceManager.playerOffline(playerName); + if (presenceManager != null) { + presenceManager.playerOffline(playerName); + } } - /** - * Restarts presence reporting when the configured backend identity changes. - */ public void reloadPresenceReporting() { if (presenceManager != null) { presenceManager.reload(); } } - /** - * Stops presence reporting without closing the global-message handler. - */ public void disablePresenceReporting() { if (presenceManager != null) { presenceManager.stop(); } } - private static long readLongSafe(String v, long def) { - if (v == null) - return def; - try { - return Long.parseLong(v); - } catch (Exception ignored) { - return def; - } + public void loadGlobalMysql() { + globalDataSync.load(); } - private void handleWireVoteDelayRejected(JsonEnvelope msg) { - if (msg.getSchema() != VotingPluginWire.SCHEMA_VERSION) { - plugin.getLogger().warning("Incompatible version with bungee/proxy, please update all servers: " - + msg.getSchema() + " != " + VotingPluginWire.SCHEMA_VERSION); - return; - } - - if (!plugin.getOptions().isProcessRewards()) { - return; - } - - VotingPluginWire.VoteDelayRejected rejected = VotingPluginWire.readVoteDelayRejected(msg); - if (rejected.uuid.isEmpty() || rejected.service.isEmpty()) { - return; - } - - UUID javaUuid; - try { - javaUuid = UUID.fromString(rejected.uuid); - } catch (IllegalArgumentException e) { - plugin.getLogger().warning("Invalid UUID in VoteDelayRejected: " + rejected.uuid); - return; - } - - VoteSite voteSite = plugin.getVoteSiteManager() - .getVoteSite(plugin.getVoteSiteManager().getVoteSiteName(true, rejected.service), true); - if (voteSite == null) { - plugin.getLogger().warning("No voting site with the service site: '" + rejected.service + "'"); - return; - } - - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid, rejected.player); - user.cache(); - user.updateName(true); - voteSite.giveWaitUntilVoteDelayRewards(user, rejected.wasOnline && user.isOnline(), true); + public GlobalDataHandler getGlobalDataHandler() { + return globalDataSync.getGlobalDataHandler(); } - /** - * Wire vote handler (Vote + VoteOnline). - */ - private void handleWireVote(JsonEnvelope msg) { - int schema = msg.getSchema(); - if (schema != VotingPluginWire.SCHEMA_VERSION) { - plugin.getLogger().warning("Incompatible version with bungee/proxy, please update all servers: " + schema - + " != " + VotingPluginWire.SCHEMA_VERSION); - return; - } - - VotingPluginWire.Vote v = VotingPluginWire.readVote(msg); - - String uuidStr = v.uuid; - String player = v.player; - String service = v.service; - - if (uuidStr == null || uuidStr.isEmpty()) { - return; - } - if (!ServiceSiteValidator.isValid(service)) { - plugin.getLogger().warning("Rejected proxy vote with invalid service site '" - + ServiceSiteValidator.sanitizeForLog(service) + "'"); - return; - } - - plugin.debug("wire vote received from " + player + "/" + uuidStr + " on " + service); - - VoteTotalsSnapshot text = VoteTotalsSnapshot.parseStorage(v.totals == null ? "" : v.totals); - @SuppressWarnings("deprecation") - UUID voteId = v.voteId != null ? v.voteId : text.getVoteUUID(); - - if (!reserveWireVote(voteId)) { - plugin.debug("Ignoring duplicate wire vote " + voteId + " for " + player + " on " + service); - return; - } - - VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(UUID.fromString(uuidStr), player); - - bungeeVotePartyCurrent = text.getVotePartyCurrent(); - bungeeVotePartyRequired = text.getVotePartyRequired(); - plugin.getServerData().setBungeeVotePartyCurrent(bungeeVotePartyCurrent); - plugin.getServerData().setBungeeVotePartyRequired(bungeeVotePartyRequired); - - user.cache(); - - boolean setTotalsOnBackend = !v.manageTotals; - user.bungeeVotePluginMessaging(service, v.time, text, setTotalsOnBackend, v.wasOnline, v.broadcast, v.num); - - if (plugin.getBungeeSettings().isPerServerPoints()) { - user.addPoints(plugin.getConfigFile().getPointsOnVote()); - } - - if (service != null && !service.isEmpty()) { - plugin.getServerData().addServiceSite(service); - } + public void checkGlobalData() { + globalDataSync.checkGlobalData(); + } - @SuppressWarnings("unused") - int _ignored = v.numberOfVotes; + public boolean checkGlobalDataTime(TimeType type, HashMap data) { + return globalDataSync.checkGlobalDataTime(type, data); } - private boolean reserveWireVote(UUID voteId) { - if (voteId == null) { - return true; - } + public boolean checkGlobalDataTimeValue(DataValue data) { + return globalDataSync.checkGlobalDataTimeValue(data); + } - long now = System.currentTimeMillis(); - long expiresAt = now + PROCESSED_VOTE_TTL_MILLIS; - - while (true) { - Long currentExpiry = processedWireVotes.get(voteId); - if (currentExpiry == null) { - if (processedWireVotes.putIfAbsent(voteId, expiresAt) == null) { - cleanupProcessedWireVotes(now); - return true; - } - continue; - } + public ConcurrentHashMap getProcessedWireVotes() { + return processedVoteCache.getProcessedVotes(); + } - if (currentExpiry > now) { - return false; - } + public int getBungeeVotePartyCurrent() { + return votePartySync == null ? plugin.getServerData().getBungeeVotePartyCurrent() : votePartySync.getCurrent(); + } - if (processedWireVotes.replace(voteId, currentExpiry, expiresAt)) { - cleanupProcessedWireVotes(now); - return true; - } - } + public int getBungeeVotePartyRequired() { + return votePartySync == null ? plugin.getServerData().getBungeeVotePartyRequired() : votePartySync.getRequired(); } - private void cleanupProcessedWireVotes(long now) { - processedWireVotes.entrySet().removeIf(entry -> entry.getValue() <= now); + public ScheduledExecutorService getTimer() { + return globalDataSync.getTimer(); } - /** - * Loads the global MySQL handler for cross-server data synchronization. - */ - public void loadGlobalMysql() { - if (plugin.getBungeeSettings().isGloblalDataEnabled()) { - if (timer != null) { - timer.shutdown(); - try { - timer.awaitTermination(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - timer.shutdownNow(); - } - timer = Executors.newScheduledThreadPool(1); - timer.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - checkGlobalData(); - } - }, 60, 10, TimeUnit.SECONDS); - timer.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - globalDataHandler.setString(plugin.getBungeeSettings().getServer(), "LastOnline", - "" + LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli()); - } - }, 1, 60, TimeUnit.MINUTES); - - if (globalDataHandler != null) { - globalDataHandler.getGlobalMysql().close(); - } + public ClientHandler getClientHandler() { + return transportManager.getClientHandler(); + } - if (plugin.getBungeeSettings().isGloblalDataUseMainMySQL() - && plugin.getStorageType().equals(UserStorage.MYSQL)) { - globalDataHandler = new GlobalDataHandler( - new GlobalMySQL("VotingPlugin_GlobalData", plugin.getMysql().getMysql()) { - @Override - public void debugEx(Exception e) { - plugin.debug(e); - } - - @Override - public void debugLog(String text) { - plugin.debug(text); - } - - @Override - public void info(String text) { - plugin.getLogger().info(text); - } - - @Override - public void logSevere(String text) { - plugin.getLogger().severe(text); - } - - @Override - public void warning(String text) { - plugin.getLogger().warning(text); - } - }); - } else { - globalDataHandler = new GlobalDataHandler( - new GlobalMySQL("VotingPlugin_GlobalData", new MysqlConfigSpigot( - plugin.getBungeeSettings().getData().getConfigurationSection("GlobalData"))) { - @Override - public void debugEx(Exception e) { - plugin.debug(e); - } - - @Override - public void debugLog(String text) { - plugin.debug(text); - } - - @Override - public void info(String text) { - plugin.getLogger().info(text); - } - - @Override - public void logSevere(String text) { - plugin.getLogger().severe(text); - } - - @Override - public void warning(String text) { - plugin.getLogger().warning(text); - } - }); - } + public SocketHandler getSocketHandler() { + return transportManager.getSocketHandler(); + } - globalDataHandler.getGlobalMysql().alterColumnType("IgnoreTime", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("MONTH", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("WEEK", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("DAY", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("FinishedProcessing", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("Processing", "VARCHAR(5)"); - globalDataHandler.getGlobalMysql().alterColumnType("LastUpdated", "MEDIUMTEXT"); - globalDataHandler.getGlobalMysql().alterColumnType("ForceUpdate", "VARCHAR(5)"); - plugin.getTimeChecker().setProcessingEnabled(false); - } + public RedisHandler getRedisHandler() { + return transportManager.getRedisHandler(); } - private void sendEnvelopeSocket(JsonEnvelope envelope) { - if (clientHandler != null) { - clientHandler.sendEnvelope(envelope); - } + public MySqlMessenger getBackendMysqlMessenger() { + return transportManager.getBackendMysqlMessenger(); } - private void sendSubChannel(String subChannel, HashMap fields) { - JsonEnvelope.Builder b = JsonEnvelope.builder(subChannel).schema(VotingPluginWire.SCHEMA_VERSION); - if (fields != null) { - for (Map.Entry e : fields.entrySet()) { - b.put(e.getKey(), e.getValue()); - } - } - globalMessageHandler.sendMessage(b.build()); + public MqttHandler getMqttHandler() { + return transportManager.getMqttHandler(); } - private static String nvl(String s) { - return s == null ? "" : s; + private void sendEnvelope(JsonEnvelope envelope) { + if (globalMessageHandler != null) { + globalMessageHandler.sendMessage(envelope); + } } } diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyRewardRegistrar.java new file mode 100644 index 0000000000..87475240dd --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/BackendProxyRewardRegistrar.java @@ -0,0 +1,16 @@ +package com.bencodez.votingplugin.backendproxy; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined backend/proxy reward paths. */ +public final class BackendProxyRewardRegistrar { + + private BackendProxyRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("BungeeVotePartyRewards", + plugin.getBungeeSettings())); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/cache/ProcessedVoteCache.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/cache/ProcessedVoteCache.java new file mode 100644 index 0000000000..0fae384443 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/cache/ProcessedVoteCache.java @@ -0,0 +1,60 @@ +package com.bencodez.votingplugin.backendproxy.cache; + +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import lombok.Getter; + +/** + * Tracks recently processed proxy vote ids so duplicate delivery is ignored. + */ +public class ProcessedVoteCache { + + private static final long DEFAULT_TTL_MILLIS = TimeUnit.MINUTES.toMillis(30); + + @Getter + private final ConcurrentHashMap processedVotes = new ConcurrentHashMap<>(); + private final long ttlMillis; + + public ProcessedVoteCache() { + this(DEFAULT_TTL_MILLIS); + } + + public ProcessedVoteCache(long ttlMillis) { + this.ttlMillis = ttlMillis; + } + + public boolean reserve(UUID voteId) { + if (voteId == null) { + return true; + } + + long now = System.currentTimeMillis(); + long expiresAt = now + ttlMillis; + + while (true) { + Long currentExpiry = processedVotes.get(voteId); + if (currentExpiry == null) { + if (processedVotes.putIfAbsent(voteId, expiresAt) == null) { + cleanup(now); + return true; + } + continue; + } + + if (currentExpiry > now) { + return false; + } + + if (processedVotes.replace(voteId, currentExpiry, expiresAt)) { + cleanup(now); + return true; + } + } + } + + private void cleanup(long now) { + processedVotes.entrySet().removeIf(entry -> entry.getValue() <= now); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/global/BackendGlobalDataSync.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/global/BackendGlobalDataSync.java new file mode 100644 index 0000000000..adaaee969a --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/global/BackendGlobalDataSync.java @@ -0,0 +1,175 @@ +package com.bencodez.votingplugin.backendproxy.global; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import com.bencodez.advancedcore.api.time.TimeType; +import com.bencodez.advancedcore.api.user.UserStorage; +import com.bencodez.advancedcore.bungeeapi.globaldata.GlobalDataHandler; +import com.bencodez.advancedcore.bungeeapi.globaldata.GlobalMySQL; +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.sql.data.DataValue; +import com.bencodez.simpleapi.sql.data.DataValueBoolean; +import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigSpigot; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.proxy.VotingPluginWire; + +import lombok.Getter; + +/** + * Owns backend global-data polling and proxy-driven time-change processing. + */ +public class BackendGlobalDataSync { + + private final VotingPluginMain plugin; + private final Consumer sender; + + @Getter + private GlobalDataHandler globalDataHandler; + @Getter + private ScheduledExecutorService timer; + + public BackendGlobalDataSync(VotingPluginMain plugin, Consumer sender) { + this.plugin = plugin; + this.sender = sender; + } + + public void checkGlobalData() { + if (globalDataHandler == null) { + return; + } + HashMap data = globalDataHandler.getExact(plugin.getBungeeSettings().getServer()); + + if (data.containsKey("ForceUpdate") && checkGlobalDataTimeValue(data.get("ForceUpdate"))) { + if (plugin.getStorageType().equals(UserStorage.MYSQL)) { + plugin.getMysql().clearCacheBasic(); + } + plugin.getUserManager().getDataManager().clearCache(); + plugin.setUpdate(true); + plugin.update(); + globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), "ForceUpdate", false); + } + + boolean forceUpdate = checkGlobalDataTime(TimeType.MONTH, data); + forceUpdate |= checkGlobalDataTime(TimeType.WEEK, data); + forceUpdate |= checkGlobalDataTime(TimeType.DAY, data); + + if (forceUpdate) { + HashMap dataToSet = new HashMap<>(); + dataToSet.put("FinishedProcessing", new DataValueBoolean(true)); + dataToSet.put("Processing", new DataValueBoolean(false)); + globalDataHandler.setData(plugin.getBungeeSettings().getServer(), dataToSet); + } + } + + public boolean checkGlobalDataTime(TimeType type, HashMap data) { + if (!data.containsKey(type.toString()) || !checkGlobalDataTimeValue(data.get(type.toString()))) { + return false; + } + + long lastUpdated = Long.valueOf(data.get("LastUpdated").getString()).longValue(); + plugin.debug("LastUpdated: " + lastUpdated); + if (LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli() - lastUpdated > 1000 * 60 * 60 * 2) { + plugin.getLogger().warning("Ignoring bungee time change since it was more than 2 hours ago"); + globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false); + return false; + } + + globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), "Processing", true); + plugin.debug("Detected time change from bungee: " + type.toString()); + plugin.getTimeChecker().forceChanged(type, false, true, true); + globalDataHandler.setBoolean(plugin.getBungeeSettings().getServer(), type.toString(), false); + + JsonEnvelope.Builder builder = JsonEnvelope.builder("TimeChangeFinished").schema(VotingPluginWire.SCHEMA_VERSION); + builder.put("server", plugin.getBungeeSettings().getServer()); + sender.accept(builder.build()); + return true; + } + + public boolean checkGlobalDataTimeValue(DataValue data) { + if (data.isBoolean()) { + return data.getBoolean(); + } + return Boolean.valueOf(data.getString()); + } + + public void load() { + if (!plugin.getBungeeSettings().isGloblalDataEnabled()) { + return; + } + + shutdownTimer(); + timer = Executors.newScheduledThreadPool(1); + timer.scheduleWithFixedDelay(this::checkGlobalData, 60, 10, TimeUnit.SECONDS); + timer.scheduleWithFixedDelay(() -> { + if (globalDataHandler != null) { + globalDataHandler.setString(plugin.getBungeeSettings().getServer(), "LastOnline", + "" + LocalDateTime.now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli()); + } + }, 1, 60, TimeUnit.MINUTES); + + closeGlobalMysql(); + + if (plugin.getBungeeSettings().isGloblalDataUseMainMySQL() + && plugin.getStorageType().equals(UserStorage.MYSQL)) { + globalDataHandler = new GlobalDataHandler(new GlobalMySQL("VotingPlugin_GlobalData", plugin.getMysql().getMysql()) { + @Override public void debugEx(Exception e) { plugin.debug(e); } + @Override public void debugLog(String text) { plugin.debug(text); } + @Override public void info(String text) { plugin.getLogger().info(text); } + @Override public void logSevere(String text) { plugin.getLogger().severe(text); } + @Override public void warning(String text) { plugin.getLogger().warning(text); } + }); + } else { + globalDataHandler = new GlobalDataHandler(new GlobalMySQL("VotingPlugin_GlobalData", + new MysqlConfigSpigot(plugin.getBungeeSettings().getData().getConfigurationSection("GlobalData"))) { + @Override public void debugEx(Exception e) { plugin.debug(e); } + @Override public void debugLog(String text) { plugin.debug(text); } + @Override public void info(String text) { plugin.getLogger().info(text); } + @Override public void logSevere(String text) { plugin.getLogger().severe(text); } + @Override public void warning(String text) { plugin.getLogger().warning(text); } + }); + } + + for (Map.Entry column : Map.of( + "IgnoreTime", "VARCHAR(5)", + "MONTH", "VARCHAR(5)", + "WEEK", "VARCHAR(5)", + "DAY", "VARCHAR(5)", + "FinishedProcessing", "VARCHAR(5)", + "Processing", "VARCHAR(5)", + "LastUpdated", "MEDIUMTEXT", + "ForceUpdate", "VARCHAR(5)").entrySet()) { + globalDataHandler.getGlobalMysql().alterColumnType(column.getKey(), column.getValue()); + } + plugin.getTimeChecker().setProcessingEnabled(false); + } + + public void close() { + closeGlobalMysql(); + } + + private void closeGlobalMysql() { + if (globalDataHandler != null) { + globalDataHandler.getGlobalMysql().close(); + } + } + + private void shutdownTimer() { + if (timer == null) { + return; + } + timer.shutdown(); + try { + timer.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + timer.shutdownNow(); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/messaging/BackendProxyMessageRouter.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/messaging/BackendProxyMessageRouter.java new file mode 100644 index 0000000000..59563bffbd --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/messaging/BackendProxyMessageRouter.java @@ -0,0 +1,263 @@ +package com.bencodez.votingplugin.backendproxy.messaging; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageListener; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.backendproxy.cache.ProcessedVoteCache; +import com.bencodez.votingplugin.backendproxy.global.BackendGlobalDataSync; +import com.bencodez.votingplugin.backendproxy.presence.BackendPresenceManager; +import com.bencodez.votingplugin.backendproxy.voteparty.BackendVotePartySync; +import com.bencodez.votingplugin.proxy.BungeeMethod; +import com.bencodez.votingplugin.proxy.VoteTotalsSnapshot; +import com.bencodez.votingplugin.proxy.VotingPluginWire; +import com.bencodez.votingplugin.user.VotingPluginUser; +import com.bencodez.votingplugin.util.ServiceSiteValidator; +import com.bencodez.votingplugin.votesites.VoteSite; + +/** + * Registers and handles backend-side proxy message routes. + */ +public class BackendProxyMessageRouter { + + private final VotingPluginMain plugin; + private final BackendPresenceManager presenceManager; + private final BackendGlobalDataSync globalDataSync; + private final BackendVotePartySync votePartySync; + private final ProcessedVoteCache processedVoteCache; + + public BackendProxyMessageRouter(VotingPluginMain plugin, BackendPresenceManager presenceManager, + BackendGlobalDataSync globalDataSync, BackendVotePartySync votePartySync, + ProcessedVoteCache processedVoteCache) { + this.plugin = plugin; + this.presenceManager = presenceManager; + this.globalDataSync = globalDataSync; + this.votePartySync = votePartySync; + this.processedVoteCache = processedVoteCache; + } + + public void register(GlobalMessageHandler messages, BungeeMethod method) { + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE) { + @Override public void onReceive(JsonEnvelope msg) { handleWireVote(msg); } + }); + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_ONLINE) { + @Override public void onReceive(JsonEnvelope msg) { handleWireVote(msg); } + }); + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_DELAY_REJECTED) { + @Override public void onReceive(JsonEnvelope msg) { handleWireVoteDelayRejected(msg); } + }); + + if (method.supportsBackendPresence()) { + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_PRESENCE_RESYNC_REQUEST) { + @Override public void onReceive(JsonEnvelope msg) { presenceManager.handleResyncRequest(msg); } + }); + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_PRESENCE_SNAPSHOT_REQUEST) { + @Override public void onReceive(JsonEnvelope msg) { presenceManager.handleSnapshotRequest(msg); } + }); + } + + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_UPDATE) { + @Override public void onReceive(JsonEnvelope msg) { handleVoteUpdate(msg); } + }); + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_BUNGEE_TIME_CHANGE) { + @Override public void onReceive(JsonEnvelope msg) { globalDataSync.checkGlobalData(); } + }); + messages.addListener(new GlobalMessageListener(VotingPluginWire.SUB_VOTE_BROADCAST) { + @Override public void onReceive(JsonEnvelope msg) { handleVoteBroadcast(msg); } + }); + messages.addListener(new GlobalMessageListener("Status") { + @Override public void onReceive(JsonEnvelope msg) { + HashMap out = new HashMap<>(); + out.put("server", nvl(msg.getFields().get("server"))); + sendSubChannel(messages, "statusokay", out); + } + }); + messages.addListener(new GlobalMessageListener("ServerName") { + @Override public void onReceive(JsonEnvelope msg) { + String server = nvl(msg.getFields().get("server")); + if (!plugin.getOptions().getServer().equals(server)) { + plugin.getLogger().warning("Server name doesn't match in BungeeSettings.yml, should be " + server); + } + } + }); + messages.addListener(new GlobalMessageListener("VotePartyBungee") { + @Override public void onReceive(JsonEnvelope msg) { votePartySync.runGlobalRewards(); } + }); + messages.addListener(new GlobalMessageListener("VotePartyBroadcast") { + @Override public void onReceive(JsonEnvelope msg) { + votePartySync.broadcast(nvl(msg.getFields().get("broadcast"))); + } + }); + } + + private void handleVoteUpdate(JsonEnvelope msg) { + VotingPluginWire.VoteUpdate update = VotingPluginWire.readVoteUpdate(msg); + String playerUuid = update.uuid; + if (playerUuid == null || playerUuid.isEmpty()) { + return; + } + + plugin.debug("pluginmessaging voteupdate received for " + playerUuid + ": " + update.votePartyCurrent + "/" + + update.votePartyRequired + " on " + update.service); + votePartySync.update(update.votePartyCurrent, update.votePartyRequired); + + VotingPluginUser user; + try { + user = plugin.getVotingPluginUserManager().getVotingPluginUser(UUID.fromString(playerUuid)); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid UUID in VoteUpdate: " + playerUuid); + return; + } + user.cache(); + user.offVote(); + + if (update.service != null && !update.service.isEmpty() && update.time > 0) { + user.setTime(plugin.getVoteSiteManager().getVoteSite(update.service, true), update.time); + } else if (update.service != null && !update.service.isEmpty() && update.time <= 0 + && plugin.getBungeeSettings().isBungeeDebug()) { + plugin.debug("Invalid last vote time received from bungee: " + update.time); + } + plugin.setUpdate(true); + } + + private void handleVoteBroadcast(JsonEnvelope msg) { + Map fields = msg.getFields(); + String uuidStr = nvl(fields.get(VotingPluginWire.K_UUID)); + String playerName = nvl(fields.get(VotingPluginWire.K_PLAYER)); + String service = nvl(fields.get(VotingPluginWire.K_SERVICE)); + if (uuidStr.isEmpty() || service.isEmpty()) { + return; + } + + UUID javaUuid; + try { + javaUuid = UUID.fromString(uuidStr); + } catch (Exception e) { + plugin.getLogger().warning("Invalid UUID in VoteBroadcast: " + uuidStr); + return; + } + + String totalsRaw = nvl(fields.get(VotingPluginWire.K_TOTALS)); + VoteTotalsSnapshot totals = totalsRaw.isEmpty() ? null : VoteTotalsSnapshot.parseStorage(totalsRaw); + VoteSite voteSite = plugin.getVoteSiteManager() + .getVoteSite(plugin.getVoteSiteManager().getVoteSiteName(true, service), true); + if (voteSite == null) { + plugin.getLogger().warning("No voting site with the service site: '" + service + "'"); + return; + } + if (!voteSite.isEnabled()) { + plugin.debug("Votesite: " + voteSite.getKey() + " is not enabled (VoteBroadcast)"); + return; + } + + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid, playerName); + user.cache(); + user.updateName(true); + if (plugin.getBroadcastHandler() == null || user.isVanished()) { + if (user.isVanished()) { + plugin.debug("Not broadcasting vote for vanished user: " + user.getPlayerName()); + } + return; + } + + boolean online = fields.containsKey(VotingPluginWire.K_WAS_ONLINE) + ? Boolean.parseBoolean(fields.get(VotingPluginWire.K_WAS_ONLINE)) : user.isOnline(); + plugin.getBroadcastHandler().broadcastVote(user.getJavaUUID(), user.getPlayerName(), + voteSite.getDisplayName(), online, totals); + } + + private void handleWireVoteDelayRejected(JsonEnvelope msg) { + if (!validSchema(msg) || !plugin.getOptions().isProcessRewards()) { + return; + } + VotingPluginWire.VoteDelayRejected rejected = VotingPluginWire.readVoteDelayRejected(msg); + if (rejected.uuid.isEmpty() || rejected.service.isEmpty()) { + return; + } + UUID javaUuid; + try { + javaUuid = UUID.fromString(rejected.uuid); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid UUID in VoteDelayRejected: " + rejected.uuid); + return; + } + VoteSite voteSite = plugin.getVoteSiteManager() + .getVoteSite(plugin.getVoteSiteManager().getVoteSiteName(true, rejected.service), true); + if (voteSite == null) { + plugin.getLogger().warning("No voting site with the service site: '" + rejected.service + "'"); + return; + } + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid, rejected.player); + user.cache(); + user.updateName(true); + voteSite.giveWaitUntilVoteDelayRewards(user, rejected.wasOnline && user.isOnline(), true); + } + + private void handleWireVote(JsonEnvelope msg) { + if (!validSchema(msg)) { + return; + } + VotingPluginWire.Vote vote = VotingPluginWire.readVote(msg); + if (vote.uuid == null || vote.uuid.isEmpty()) { + return; + } + if (!ServiceSiteValidator.isValid(vote.service)) { + plugin.getLogger().warning("Rejected proxy vote with invalid service site '" + + ServiceSiteValidator.sanitizeForLog(vote.service) + "'"); + return; + } + + plugin.debug("wire vote received from " + vote.player + "/" + vote.uuid + " on " + vote.service); + VoteTotalsSnapshot totals = VoteTotalsSnapshot.parseStorage(vote.totals == null ? "" : vote.totals); + @SuppressWarnings("deprecation") + UUID voteId = vote.voteId != null ? vote.voteId : totals.getVoteUUID(); + if (!processedVoteCache.reserve(voteId)) { + plugin.debug("Ignoring duplicate wire vote " + voteId + " for " + vote.player + " on " + vote.service); + return; + } + + UUID javaUuid; + try { + javaUuid = UUID.fromString(vote.uuid); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid UUID in proxy vote: " + vote.uuid); + return; + } + VotingPluginUser user = plugin.getVotingPluginUserManager().getVotingPluginUser(javaUuid, vote.player); + votePartySync.replace(totals.getVotePartyCurrent(), totals.getVotePartyRequired()); + user.cache(); + user.bungeeVotePluginMessaging(vote.service, vote.time, totals, !vote.manageTotals, + vote.wasOnline, vote.broadcast, vote.num); + if (plugin.getBungeeSettings().isPerServerPoints()) { + user.addPoints(plugin.getConfigFile().getPointsOnVote()); + } + if (vote.service != null && !vote.service.isEmpty()) { + plugin.getServerData().addServiceSite(vote.service); + } + } + + private boolean validSchema(JsonEnvelope msg) { + if (msg.getSchema() == VotingPluginWire.SCHEMA_VERSION) { + return true; + } + plugin.getLogger().warning("Incompatible version with bungee/proxy, please update all servers: " + + msg.getSchema() + " != " + VotingPluginWire.SCHEMA_VERSION); + return false; + } + + private void sendSubChannel(GlobalMessageHandler messages, String subChannel, HashMap fields) { + JsonEnvelope.Builder builder = JsonEnvelope.builder(subChannel).schema(VotingPluginWire.SCHEMA_VERSION); + for (Map.Entry entry : fields.entrySet()) { + builder.put(entry.getKey(), entry.getValue()); + } + messages.sendMessage(builder.build()); + } + + private static String nvl(String value) { + return value == null ? "" : value; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransport.java new file mode 100644 index 0000000000..5e7b40b49b --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransport.java @@ -0,0 +1,16 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; + +/** + * Transport used by a backend server to communicate with the VotingPlugin proxy. + */ +public interface BackendProxyTransport { + + void start(GlobalMessageHandler messageHandler); + + void send(JsonEnvelope envelope); + + void close(); +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransportManager.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransportManager.java new file mode 100644 index 0000000000..e8377f7538 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/BackendProxyTransportManager.java @@ -0,0 +1,86 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.mqtt.MqttHandler; +import com.bencodez.simpleapi.servercomm.mysql.MySqlMessenger; +import com.bencodez.simpleapi.servercomm.redis.RedisHandler; +import com.bencodez.simpleapi.servercomm.sockets.ClientHandler; +import com.bencodez.simpleapi.servercomm.sockets.SocketHandler; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.proxy.BungeeMethod; + +/** + * Selects and owns the active backend-to-proxy transport. + */ +public class BackendProxyTransportManager { + + private final VotingPluginMain plugin; + private BackendProxyTransport transport; + + public BackendProxyTransportManager(VotingPluginMain plugin) { + this.plugin = plugin; + } + + public void start(BungeeMethod method, GlobalMessageHandler messageHandler) { + close(); + switch (method) { + case MYSQL: + transport = new MysqlBackendProxyTransport(plugin); + break; + case PLUGINMESSAGING: + transport = new PluginMessagingBackendProxyTransport(plugin); + break; + case SOCKETS: + transport = new SocketBackendProxyTransport(plugin); + break; + case REDIS: + transport = new RedisBackendProxyTransport(plugin); + break; + case MQTT: + transport = new MqttBackendProxyTransport(plugin); + break; + default: + throw new IllegalArgumentException("Unsupported backend proxy method: " + method); + } + transport.start(messageHandler); + } + + public void send(JsonEnvelope envelope) { + if (transport != null) { + transport.send(envelope); + } + } + + public void close() { + if (transport != null) { + transport.close(); + transport = null; + } + } + + public ClientHandler getClientHandler() { + return transport instanceof SocketBackendProxyTransport + ? ((SocketBackendProxyTransport) transport).getClientHandler() : null; + } + + public SocketHandler getSocketHandler() { + return transport instanceof SocketBackendProxyTransport + ? ((SocketBackendProxyTransport) transport).getSocketHandler() : null; + } + + public RedisHandler getRedisHandler() { + return transport instanceof RedisBackendProxyTransport + ? ((RedisBackendProxyTransport) transport).getRedisHandler() : null; + } + + public MySqlMessenger getBackendMysqlMessenger() { + return transport instanceof MysqlBackendProxyTransport + ? ((MysqlBackendProxyTransport) transport).getMessenger() : null; + } + + public MqttHandler getMqttHandler() { + return transport instanceof MqttBackendProxyTransport + ? ((MqttBackendProxyTransport) transport).getMqttHandler() : null; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MqttBackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MqttBackendProxyTransport.java new file mode 100644 index 0000000000..f092e77da2 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MqttBackendProxyTransport.java @@ -0,0 +1,58 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import org.eclipse.paho.client.mqttv3.MqttException; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.mqtt.MqttHandler; +import com.bencodez.simpleapi.servercomm.mqtt.MqttServerComm; +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +public class MqttBackendProxyTransport implements BackendProxyTransport { + + private final VotingPluginMain plugin; + @Getter + private MqttHandler mqttHandler; + + public MqttBackendProxyTransport(VotingPluginMain plugin) { + this.plugin = plugin; + } + + @Override + public void start(GlobalMessageHandler messageHandler) { + try { + String id = plugin.getBungeeSettings().getMqttClientID(); + if (id.isEmpty()) { + id = plugin.getOptions().getServer(); + } + mqttHandler = new MqttHandler(new MqttServerComm(id, plugin.getBungeeSettings().getMqttBrokerURL(), + plugin.getBungeeSettings().getMqttUsername(), plugin.getBungeeSettings().getMqttPassword()), 2); + mqttHandler.subscribeEnvelopes(plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/" + + plugin.getOptions().getServer(), (topic, envelope) -> messageHandler.onMessage(envelope)); + } catch (MqttException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void send(JsonEnvelope envelope) { + if (mqttHandler == null) { + return; + } + try { + mqttHandler.publishEnvelope(plugin.getBungeeSettings().getMqttPrefix() + "votingplugin/servers/proxy", + envelope); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void close() { + // Preserve current MQTT lifecycle behavior. + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MysqlBackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MysqlBackendProxyTransport.java new file mode 100644 index 0000000000..d55b2e88f8 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/MysqlBackendProxyTransport.java @@ -0,0 +1,58 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import java.sql.SQLException; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.mysql.MySqlMessenger; +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +public class MysqlBackendProxyTransport implements BackendProxyTransport { + + private final VotingPluginMain plugin; + @Getter + private MySqlMessenger messenger; + + public MysqlBackendProxyTransport(VotingPluginMain plugin) { + this.plugin = plugin; + } + + @Override + public void start(GlobalMessageHandler messageHandler) { + plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel()); + try { + messenger = new MySqlMessenger("VotingPlugin", + plugin.getMysql().getMysql().getConnectionManager().getDataSource(), + MySqlMessenger.Mode.BACKEND, plugin.getOptions().getServer(), msg -> { + if (plugin.getBungeeSettings().isBungeeDebug()) { + plugin.debug("Proxy sent envelope: " + msg.envelope.getSubChannel() + " " + + msg.envelope.getFields()); + } + messageHandler.onMessage(msg.envelope); + }); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public void send(JsonEnvelope envelope) { + if (messenger == null) { + return; + } + try { + messenger.sendToProxy(envelope); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public void close() { + if (messenger != null) { + messenger.shutdown(); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/PluginMessagingBackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/PluginMessagingBackendProxyTransport.java new file mode 100644 index 0000000000..6616ff748d --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/PluginMessagingBackendProxyTransport.java @@ -0,0 +1,45 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import java.io.File; + +import com.bencodez.simpleapi.encryption.EncryptionHandler; +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.pluginmessage.PluginMessageHandler; +import com.bencodez.votingplugin.VotingPluginMain; + +public class PluginMessagingBackendProxyTransport implements BackendProxyTransport { + + private final VotingPluginMain plugin; + + public PluginMessagingBackendProxyTransport(VotingPluginMain plugin) { + this.plugin = plugin; + } + + @Override + public void start(GlobalMessageHandler messageHandler) { + plugin.registerBungeeChannels(plugin.getBungeeSettings().getPluginMessagingChannel()); + if (plugin.getBungeeSettings().isPluginMessageEncryption()) { + EncryptionHandler encryptionHandler = new EncryptionHandler(plugin.getName(), + new File(plugin.getDataFolder(), "secretkey.key")); + plugin.getPluginMessaging().setEncryptionHandler(encryptionHandler); + } + plugin.getPluginMessaging().setDebug(plugin.getBungeeSettings().isBungeeDebug()); + plugin.getPluginMessaging().add(new PluginMessageHandler() { + @Override + public void onReceive(JsonEnvelope envelope) { + messageHandler.onMessage(envelope); + } + }); + } + + @Override + public void send(JsonEnvelope envelope) { + plugin.getPluginMessaging().sendEnvelope(envelope); + } + + @Override + public void close() { + // Plugin messaging is owned by the plugin lifecycle. + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/RedisBackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/RedisBackendProxyTransport.java new file mode 100644 index 0000000000..34873e5282 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/RedisBackendProxyTransport.java @@ -0,0 +1,57 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.redis.RedisHandler; +import com.bencodez.simpleapi.servercomm.redis.RedisListener; +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +public class RedisBackendProxyTransport implements BackendProxyTransport { + + private final VotingPluginMain plugin; + @Getter + private RedisHandler redisHandler; + private Thread listenerThread; + + public RedisBackendProxyTransport(VotingPluginMain plugin) { + this.plugin = plugin; + } + + @Override + public void start(GlobalMessageHandler messageHandler) { + redisHandler = new RedisHandler(plugin.getBungeeSettings().getRedisHost(), + plugin.getBungeeSettings().getRedisPort(), plugin.getBungeeSettings().getRedisUsername(), + plugin.getBungeeSettings().getRedisPassword(), plugin.getBungeeSettings().getRedisdbindex()) { + @Override + public void debug(String message) { + if (plugin.getBungeeSettings().isBungeeDebug()) { + plugin.debug(message); + } + } + }; + + listenerThread = new Thread(() -> { + if (plugin.isEnabled()) { + RedisListener listener = redisHandler.createEnvelopeListener( + plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin_" + plugin.getBungeeSettings().getServer(), + (ch, envelope) -> messageHandler.onMessage(envelope)); + redisHandler.loadListener(listener); + } + }, "VotingPlugin-Redis-Backend"); + listenerThread.start(); + } + + @Override + public void send(JsonEnvelope envelope) { + if (redisHandler != null) { + redisHandler.publishEnvelope(plugin.getBungeeSettings().getRedisPrefix() + "VotingPlugin", envelope); + } + } + + @Override + public void close() { + // Preserve current Redis lifecycle behavior; the listener is owned by RedisHandler. + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/SocketBackendProxyTransport.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/SocketBackendProxyTransport.java new file mode 100644 index 0000000000..c4342ca58a --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/transport/SocketBackendProxyTransport.java @@ -0,0 +1,66 @@ +package com.bencodez.votingplugin.backendproxy.transport; + +import java.io.File; + +import com.bencodez.simpleapi.encryption.EncryptionHandler; +import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope; +import com.bencodez.simpleapi.servercomm.global.GlobalMessageHandler; +import com.bencodez.simpleapi.servercomm.sockets.ClientHandler; +import com.bencodez.simpleapi.servercomm.sockets.SocketHandler; +import com.bencodez.simpleapi.servercomm.sockets.SocketReceiver; +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +public class SocketBackendProxyTransport implements BackendProxyTransport { + + private final VotingPluginMain plugin; + @Getter + private ClientHandler clientHandler; + @Getter + private SocketHandler socketHandler; + + public SocketBackendProxyTransport(VotingPluginMain plugin) { + this.plugin = plugin; + } + + @Override + public void start(GlobalMessageHandler messageHandler) { + EncryptionHandler encryptionHandler = new EncryptionHandler(plugin.getName(), + new File(plugin.getDataFolder(), "secretkey.key")); + clientHandler = new ClientHandler(plugin.getBungeeSettings().getBungeeServerHost(), + plugin.getBungeeSettings().getBungeeServerPort(), encryptionHandler, + plugin.getBungeeSettings().isBungeeDebug()); + socketHandler = new SocketHandler("vp-socket", plugin.getBungeeSettings().getSpigotServerHost(), + plugin.getBungeeSettings().getSpigotServerPort(), encryptionHandler, + plugin.getBungeeSettings().isBungeeDebug()) { + @Override + public void log(String str) { + plugin.getLogger().info(str); + } + }; + socketHandler.add(new SocketReceiver() { + @Override + public void onReceiveEnvelope(JsonEnvelope envelope) { + messageHandler.onMessage(envelope); + } + }); + } + + @Override + public void send(JsonEnvelope envelope) { + if (clientHandler != null) { + clientHandler.sendEnvelope(envelope); + } + } + + @Override + public void close() { + if (socketHandler != null) { + socketHandler.closeConnection(); + } + if (clientHandler != null) { + clientHandler.stopConnection(); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/voteparty/BackendVotePartySync.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/voteparty/BackendVotePartySync.java new file mode 100644 index 0000000000..87c10392f4 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/backendproxy/voteparty/BackendVotePartySync.java @@ -0,0 +1,64 @@ +package com.bencodez.votingplugin.backendproxy.voteparty; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.bencodez.advancedcore.api.misc.MiscUtils; +import com.bencodez.advancedcore.api.rewards.RewardBuilder; +import com.bencodez.votingplugin.VotingPluginMain; + +import lombok.Getter; + +/** + * Owns backend-side cached proxy vote-party state and proxy vote-party events. + */ +public class BackendVotePartySync { + + private final VotingPluginMain plugin; + + @Getter + private int current; + @Getter + private int required; + + public BackendVotePartySync(VotingPluginMain plugin) { + this.plugin = plugin; + current = plugin.getServerData().getBungeeVotePartyCurrent(); + required = plugin.getServerData().getBungeeVotePartyRequired(); + } + + public void update(int votePartyCurrent, int votePartyRequired) { + if (votePartyCurrent >= 0 || current == -2) { + current = votePartyCurrent; + } + if (votePartyRequired >= 0 || required == -2) { + required = votePartyRequired; + } + persist(); + } + + public void replace(int votePartyCurrent, int votePartyRequired) { + current = votePartyCurrent; + required = votePartyRequired; + persist(); + } + + public void persist() { + plugin.getServerData().setBungeeVotePartyCurrent(current); + plugin.getServerData().setBungeeVotePartyRequired(required); + } + + public void runGlobalRewards() { + for (final String command : plugin.getBungeeSettings().getBungeeVotePartyGlobalCommands()) { + plugin.getBukkitScheduler().runTask(plugin, + () -> Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command)); + } + for (Player player : Bukkit.getOnlinePlayers()) { + new RewardBuilder(plugin.getBungeeSettings().getData(), "BungeeVotePartyRewards").send(player); + } + } + + public void broadcast(String message) { + MiscUtils.getInstance().broadcast(message); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/DirectlyDefinedRewardPath.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/DirectlyDefinedRewardPath.java new file mode 100644 index 0000000000..b48ec0edaa --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/rewards/DirectlyDefinedRewardPath.java @@ -0,0 +1,39 @@ +package com.bencodez.votingplugin.rewards; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.advancedcore.api.rewards.DirectlyDefinedReward; +import com.bencodez.simpleapi.file.YMLFile; + +/** + * Creates directly-defined reward editors backed by a VotingPlugin YAML file. + */ +public final class DirectlyDefinedRewardPath { + + private DirectlyDefinedRewardPath() { + } + + public static DirectlyDefinedReward of(String path, YMLFile file) { + return new DirectlyDefinedReward(path) { + @Override + public void createSection(String key) { + file.createSection(key); + } + + @Override + public ConfigurationSection getFileData() { + return file.getData(); + } + + @Override + public void save() { + file.saveData(); + } + + @Override + public void setData(String dataPath, Object value) { + file.setValue(dataPath, value); + } + }; + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/SpecialRewardsRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/SpecialRewardsRewardRegistrar.java new file mode 100644 index 0000000000..c7b6a6bdd9 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/SpecialRewardsRewardRegistrar.java @@ -0,0 +1,37 @@ +package com.bencodez.votingplugin.specialrewards; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined general and legacy special reward paths. */ +public final class SpecialRewardsRewardRegistrar { + + private SpecialRewardsRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + String[] simplePaths = { + "VoteCoolDownEndedReward", "LoginRewards", "LogoutRewards", + "AllSites", "AlmostAllSites", "FirstVote", "FirstVoteToday", "NameMCLikeReward.Rewards" + }; + for (String path : simplePaths) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of(path, plugin.getSpecialRewardsConfig())); + } + + registerLegacyGroup(plugin, "Cumulative"); + registerLegacyGroup(plugin, "Milestones"); + } + + private static void registerLegacyGroup(VotingPluginMain plugin, String key) { + ConfigurationSection section = plugin.getSpecialRewardsConfig().getData().getConfigurationSection(key); + if (section == null) { + return; + } + for (String entry : section.getKeys(false)) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of(key + "." + entry + ".Rewards", + plugin.getSpecialRewardsConfig())); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votemilestones/VoteMilestoneRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votemilestones/VoteMilestoneRewardRegistrar.java new file mode 100644 index 0000000000..8073e5b5e8 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votemilestones/VoteMilestoneRewardRegistrar.java @@ -0,0 +1,25 @@ +package com.bencodez.votingplugin.specialrewards.votemilestones; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined VoteMilestones reward paths. */ +public final class VoteMilestoneRewardRegistrar { + + private VoteMilestoneRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + ConfigurationSection section = plugin.getSpecialRewardsConfig().getData() + .getConfigurationSection("VoteMilestones"); + if (section == null) { + return; + } + for (String milestoneId : section.getKeys(false)) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "VoteMilestones." + milestoneId + ".Rewards", plugin.getSpecialRewardsConfig())); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyRewardRegistrar.java new file mode 100644 index 0000000000..50ddd13fe9 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/voteparty/VotePartyRewardRegistrar.java @@ -0,0 +1,16 @@ +package com.bencodez.votingplugin.specialrewards.voteparty; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined vote-party reward paths. */ +public final class VotePartyRewardRegistrar { + + private VotePartyRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("VoteParty.Rewards", + plugin.getSpecialRewardsConfig())); + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakRewardRegistrar.java new file mode 100644 index 0000000000..f74fb5a9d0 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/specialrewards/votestreak/VoteStreakRewardRegistrar.java @@ -0,0 +1,67 @@ +package com.bencodez.votingplugin.specialrewards.votestreak; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined vote-streak reward paths. */ +public final class VoteStreakRewardRegistrar { + + private VoteStreakRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + registerCurrent(plugin); + registerLegacy(plugin); + } + + private static void registerCurrent(VotingPluginMain plugin) { + ConfigurationSection streaks = plugin.getSpecialRewardsConfig().getData().getConfigurationSection("VoteStreaks"); + if (streaks == null) { + return; + } + + for (String streakId : streaks.getKeys(false)) { + if ("ProgressGroups".equalsIgnoreCase(streakId) || streaks.getConfigurationSection(streakId) == null) { + continue; + } + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "VoteStreaks." + streakId + ".Rewards", plugin.getSpecialRewardsConfig())); + } + + ConfigurationSection groups = streaks.getConfigurationSection("ProgressGroups"); + if (groups == null) { + return; + } + for (String groupId : groups.getKeys(false)) { + ConfigurationSection group = groups.getConfigurationSection(groupId); + if (group == null) { + continue; + } + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "VoteStreaks.ProgressGroups." + groupId + ".LostRewards", plugin.getSpecialRewardsConfig())); + ConfigurationSection milestones = group.getConfigurationSection("Milestones"); + if (milestones == null) { + continue; + } + for (String milestoneId : milestones.getKeys(false)) { + if (milestones.getConfigurationSection(milestoneId) == null) { + continue; + } + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "VoteStreaks.ProgressGroups." + groupId + ".Milestones." + milestoneId + ".Rewards", + plugin.getSpecialRewardsConfig())); + } + } + } + + private static void registerLegacy(VotingPluginMain plugin) { + for (String type : new String[] { "Day", "Week", "Month" }) { + for (String value : plugin.getSpecialRewardsConfig().getVoteStreakVotes(type)) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "VoteStreak." + type + "." + value + ".Rewards", plugin.getSpecialRewardsConfig())); + } + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterRewardRegistrar.java new file mode 100644 index 0000000000..c03120151c --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/topvoter/TopVoterRewardRegistrar.java @@ -0,0 +1,26 @@ +package com.bencodez.votingplugin.topvoter; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined top-voter award reward paths. */ +public final class TopVoterRewardRegistrar { + + private TopVoterRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + for (String path : plugin.getSpecialRewardsConfig().getMonthlyPossibleRewardPlaces()) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + plugin.getSpecialRewardsConfig().getMonthlyAwardRewardsPath(path), plugin.getSpecialRewardsConfig())); + } + for (String path : plugin.getSpecialRewardsConfig().getWeeklyPossibleRewardPlaces()) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + plugin.getSpecialRewardsConfig().getWeeklyAwardRewardsPath(path), plugin.getSpecialRewardsConfig())); + } + for (String path : plugin.getSpecialRewardsConfig().getDailyPossibleRewardPlaces()) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + plugin.getSpecialRewardsConfig().getDailyAwardRewardsPath(path), plugin.getSpecialRewardsConfig())); + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/votereminding/VoteReminderRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votereminding/VoteReminderRewardRegistrar.java new file mode 100644 index 0000000000..0e79779acd --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votereminding/VoteReminderRewardRegistrar.java @@ -0,0 +1,31 @@ +package com.bencodez.votingplugin.votereminding; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.advancedcore.api.rewards.DirectlyDefinedReward; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined vote reminder reward paths. */ +public final class VoteReminderRewardRegistrar { + + private VoteReminderRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + plugin.addDirectlyDefinedRewards(new DirectlyDefinedReward("VoteReminderOptions.Defaults.Rewards") { + @Override public void createSection(String key) { plugin.getConfigFile().saveData(); } + @Override public ConfigurationSection getFileData() { return plugin.getConfigFile().getData(); } + @Override public void save() { plugin.getConfigFile().saveData(); } + @Override public void setData(String path, Object value) { plugin.getConfigFile().setValue(path, value); } + }); + + ConfigurationSection reminders = plugin.getConfigFile().getData().getConfigurationSection("VoteReminders"); + if (reminders != null) { + for (String key : reminders.getKeys(false)) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("VoteReminders." + key + ".Rewards", + plugin.getConfigFile())); + } + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/voteshop/VoteShopRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/voteshop/VoteShopRewardRegistrar.java new file mode 100644 index 0000000000..e477c0854e --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/voteshop/VoteShopRewardRegistrar.java @@ -0,0 +1,35 @@ +package com.bencodez.votingplugin.voteshop; + +import org.bukkit.configuration.ConfigurationSection; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined vote-shop reward paths. */ +public final class VoteShopRewardRegistrar { + + private VoteShopRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + for (String identifier : plugin.getShopFile().getShopIdentifiers()) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("Shop." + identifier + ".Rewards", + plugin.getShopFile())); + } + + ConfigurationSection categories = plugin.getShopFile().getCategoriesSection(); + if (categories == null) { + return; + } + for (String category : categories.getKeys(false)) { + ConfigurationSection shop = plugin.getShopFile().getCategoryShopSection(category); + if (shop == null) { + continue; + } + for (String item : shop.getKeys(false)) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of( + "Categories." + category + "." + item + ".Rewards", plugin.getShopFile())); + } + } + } +} diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteRewardRegistrar.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteRewardRegistrar.java new file mode 100644 index 0000000000..2ce1301c78 --- /dev/null +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteRewardRegistrar.java @@ -0,0 +1,25 @@ +package com.bencodez.votingplugin.votesites; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.rewards.DirectlyDefinedRewardPath; + +/** Registers directly-defined vote-site reward paths. */ +public final class VoteSiteRewardRegistrar { + + private VoteSiteRewardRegistrar() { + } + + public static void register(VotingPluginMain plugin) { + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("AnySiteRewards", plugin.getSpecialRewardsConfig())); + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of("EverySiteReward", plugin.getConfigVoteSites())); + + for (VoteSite site : plugin.getVoteSiteManager().getVoteSites()) { + String base = "VoteSites." + site.getKey(); + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of(base + ".Rewards", plugin.getConfigVoteSites())); + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of(base + ".WaitUntilVoteDelayRewards", + plugin.getConfigVoteSites())); + plugin.addDirectlyDefinedRewards(DirectlyDefinedRewardPath.of(base + ".CoolDownEndRewards", + plugin.getConfigVoteSites())); + } + } +} diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backendproxy/cache/ProcessedVoteCacheTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backendproxy/cache/ProcessedVoteCacheTest.java new file mode 100644 index 0000000000..f98fce6761 --- /dev/null +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/backendproxy/cache/ProcessedVoteCacheTest.java @@ -0,0 +1,41 @@ +package com.bencodez.votingplugin.tests.backendproxy.cache; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import com.bencodez.votingplugin.backendproxy.cache.ProcessedVoteCache; + +public class ProcessedVoteCacheTest { + + @Test + public void duplicateVoteIdIsRejected() { + ProcessedVoteCache cache = new ProcessedVoteCache(); + UUID voteId = UUID.randomUUID(); + assertTrue(cache.reserve(voteId)); + assertFalse(cache.reserve(voteId)); + assertEquals(1, cache.getProcessedVotes().size()); + } + + @Test + public void nullVoteIdsRemainCompatible() { + ProcessedVoteCache cache = new ProcessedVoteCache(); + assertTrue(cache.reserve(null)); + assertTrue(cache.reserve(null)); + assertTrue(cache.getProcessedVotes().isEmpty()); + } + + @Test + public void expiredVoteIdCanBeReservedAgain() throws Exception { + ProcessedVoteCache cache = new ProcessedVoteCache(1); + UUID voteId = UUID.randomUUID(); + assertTrue(cache.reserve(voteId)); + Thread.sleep(5); + assertTrue(cache.reserve(voteId)); + assertEquals(1, cache.getProcessedVotes().size()); + } +}