Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
5205dee
Extract backend processed vote cache
BenCodez Aug 24, 2026
211919e
Extract backend vote party sync state
BenCodez Aug 24, 2026
ef0dfc6
Extract backend global data sync
BenCodez Aug 24, 2026
e305540
Add backend proxy transport contract
BenCodez Aug 24, 2026
6378e97
Extract backend MySQL transport
BenCodez Aug 24, 2026
c44c347
Extract plugin messaging backend transport
BenCodez Aug 24, 2026
16becd3
Extract socket backend transport
BenCodez Aug 24, 2026
98363e1
Extract Redis backend transport
BenCodez Aug 24, 2026
280ded8
Extract MQTT backend transport
BenCodez Aug 24, 2026
fa5f35e
Add backend proxy transport manager
BenCodez Aug 24, 2026
94213ea
Extract backend proxy message routing
BenCodez Aug 24, 2026
2bd8c06
Make BackendProxyHandler a coordinator
BenCodez Aug 24, 2026
ad2b360
Add directly defined reward path helper
BenCodez Aug 24, 2026
1f538a5
Move vote reminder reward path registration
BenCodez Aug 24, 2026
8473494
Move vote site reward path registration
BenCodez Aug 24, 2026
cce2ad3
Move general special reward path registration
BenCodez Aug 24, 2026
b4304f6
Move vote party reward path registration
BenCodez Aug 24, 2026
87dc19c
Move vote milestone reward path registration
BenCodez Aug 24, 2026
b91ae03
Move vote streak reward path registration
BenCodez Aug 24, 2026
42f7b93
Move top voter reward path registration
BenCodez Aug 24, 2026
2fd7270
Move vote shop reward path registration
BenCodez Aug 24, 2026
7164e2c
Move backend proxy reward path registration
BenCodez Aug 24, 2026
ec717f6
Test processed vote cache extraction
BenCodez Aug 24, 2026
1ba3f87
Run backend proxy component refactor
BenCodez Aug 24, 2026
408cb93
Split backend proxy components and reward path ownership
github-actions[bot] Aug 24, 2026
b2d1879
Remove temporary refactor workflow
github-actions[bot] Aug 24, 2026
30ca646
Strengthen processed vote cache tests
BenCodez Aug 24, 2026
f0cea43
Extract Points reward injection
BenCodez Aug 24, 2026
e0cc2bd
Extract VoteBossBar reward injection
BenCodez Aug 24, 2026
4ddf5b7
Extract Webhook reward injection
BenCodez Aug 24, 2026
250b433
Extract VoteTotal requirement injection
BenCodez Aug 24, 2026
5955abd
Extract reward total placeholders
BenCodez Aug 24, 2026
7937a02
Add VotingPlugin reward registration coordinator
BenCodez Aug 24, 2026
a9c33ba
Run VotingPlugin reward registration refactor
BenCodez Aug 24, 2026
707d49d
Move VotingPlugin reward registration out of main
github-actions[bot] Aug 24, 2026
789e4f4
Remove temporary reward refactor workflow
github-actions[bot] Aug 24, 2026
9f00eb4
Extract top voter ranking logic
BenCodez Aug 24, 2026
150dea6
Extract top voter loading logic
BenCodez Aug 24, 2026
27c8cb2
Preserve top voter place parse diagnostics
BenCodez Aug 24, 2026
d8c3e1c
Test top voter month column parsing
BenCodez Aug 24, 2026
d36f643
Run top voter handler refactor
BenCodez Aug 24, 2026
d31cbb6
Run top voter refactor against master PR
BenCodez Aug 24, 2026
68fd9b4
Fix top voter refactor leftovers
BenCodez Aug 24, 2026
7fb50dc
Split top voter loading and ranking
github-actions[bot] Aug 24, 2026
01215e2
Remove temporary top voter refactor workflow
github-actions[bot] Aug 24, 2026
3845cd9
Add version info component
BenCodez Aug 25, 2026
04c2e46
Add config health component
BenCodez Aug 25, 2026
59281e5
Add Votifier integration component
BenCodez Aug 25, 2026
de0b9d9
Add vote log manager
BenCodez Aug 25, 2026
d6f65ae
Add webhook lifecycle manager
BenCodez Aug 25, 2026
98de92c
Add lifecycle refactor workflow
BenCodez Aug 25, 2026
1a951c9
Move lifecycle services out of VotingPluginMain
github-actions[bot] Aug 25, 2026
cb0eac2
Remove temporary lifecycle refactor workflow
github-actions[bot] Aug 25, 2026
4628f94
Add background task component
BenCodez Aug 25, 2026
ca3ddb9
Test background task state
BenCodez Aug 25, 2026
4ddf669
Add background task refactor workflow
BenCodez Aug 25, 2026
e737974
Extract VotingPlugin background task
github-actions[bot] Aug 25, 2026
656e9b0
Remove temporary background task workflow
github-actions[bot] Aug 25, 2026
2d80136
Add VoteParty state component
BenCodez Aug 25, 2026
ac0b777
Add VoteParty reward handler
BenCodez Aug 25, 2026
6f142e7
Add VoteParty refactor workflow
BenCodez Aug 25, 2026
a895b57
Split VoteParty state and reward handling
github-actions[bot] Aug 25, 2026
9f223b8
Remove temporary VoteParty refactor workflow
github-actions[bot] Aug 25, 2026
e9224eb
Add generated comment cleanup workflow
BenCodez Aug 25, 2026
6f0284e
Keep descriptive user Javadoc
BenCodez Aug 25, 2026
bdb9045
Limit generated comment removal to comment blocks
BenCodez Aug 25, 2026
057c1c7
Remove remaining generated comment boilerplate
github-actions[bot] Aug 25, 2026
67666ca
Remove temporary comment cleanup workflow
github-actions[bot] Aug 25, 2026
ff205da
Preserve line endings in generated comment cleanup
BenCodez Aug 25, 2026
5f8d196
Preserve line endings in comment cleanup
github-actions[bot] Aug 25, 2026
09db997
Remove temporary line ending cleanup workflow
github-actions[bot] Aug 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,442 changes: 171 additions & 1,271 deletions VotingPlugin/src/main/java/com/bencodez/votingplugin/VotingPluginMain.java

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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()));
}
}
Original file line number Diff line number Diff line change
@@ -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<UUID, Long> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<JsonEnvelope> sender;

@Getter
private GlobalDataHandler globalDataHandler;
@Getter
private ScheduledExecutorService timer;

public BackendGlobalDataSync(VotingPluginMain plugin, Consumer<JsonEnvelope> sender) {
this.plugin = plugin;
this.sender = sender;
}

public void checkGlobalData() {
if (globalDataHandler == null) {
return;
}
HashMap<String, DataValue> 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<String, DataValue> 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<String, DataValue> 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<String, String> 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();
}
}
Loading