Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
164 changes: 20 additions & 144 deletions src/main/java/com/globalchat/GlobalChatInfoPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,9 @@ public class GlobalChatInfoPanel extends PluginPanel {
private final Client client;
private final OkHttpClient httpClient;
private final Gson gson;
private JLabel totalUsersLabel;
private JLabel currentWorldUsersLabel;
private JLabel topWorldLabel;
private JLabel readOnlyStatusLabel;
private JLabel connectionStatusLabel;
private JLabel connectionLimitsLabel;
private Timer userCountUpdateTimer;
private Timer connectionStatusTimer;
private ConnectionStatsResponse connectionStats = null;
private boolean hasReceivedValidData = false;
Expand Down Expand Up @@ -571,123 +567,28 @@ private JPanel createUserCountSection() {
// panel.add(topWorldLabel, gbc);

// Start periodic updates
// startUserCountUpdates(); // Commented out - online users disabled
startConnectionStatusUpdates();

return panel;
}

private void startUserCountUpdates() {
// Initial update
updateUserCounts();

// Update every 30 seconds
userCountUpdateTimer = new Timer(30000, e -> {
updateUserCounts();
fetchConnectionStats();
});
userCountUpdateTimer.start();
}

private void updateUserCounts() {
// Skip if we don't have the required dependencies (test constructor)
if (httpClient == null || gson == null) {
return;
}

Request request = new Request.Builder()
.url("https://global-chat-frontend.vercel.app/api/user-counts")
.build();

httpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, java.io.IOException e) {
log.debug("Failed to fetch user counts: {}", e.getMessage());
}

@Override
public void onResponse(okhttp3.Call call, Response response) throws java.io.IOException {
try {
if (response.isSuccessful() && response.body() != null) {
String responseBody = response.body().string();
UserCountResponse userCountResponse = gson.fromJson(responseBody, UserCountResponse.class);

if (userCountResponse != null) {
// Get current world count if available
String currentWorldId = getCurrentWorldId();
int currentWorldCount = 0;
if (currentWorldId != null && userCountResponse.worldCounts != null) {
Integer worldCount = userCountResponse.worldCounts.get(currentWorldId);
if (worldCount != null) {
currentWorldCount = worldCount;
}
}

// Find top world
String topWorldId = null;
int topWorldCount = 0;
if (userCountResponse.worldCounts != null) {
for (Map.Entry<String, Integer> entry : userCountResponse.worldCounts.entrySet()) {
if (entry.getValue() > topWorldCount) {
topWorldCount = entry.getValue();
topWorldId = entry.getKey();
}
}
}

UserCountData data = new UserCountData(userCountResponse.totalOnline, currentWorldCount, currentWorldId, topWorldId, topWorldCount);

// Update UI on EDT
javax.swing.SwingUtilities.invokeLater(() -> {
// Only update if the labels exist (not commented out)
if (totalUsersLabel != null) {
if (data.totalOnline > 0) {
totalUsersLabel.setText("Total Online: " + data.totalOnline + " players");
totalUsersLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
} else {
totalUsersLabel.setText("Total Online: Unavailable");
totalUsersLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
}
}

if (data.currentWorldId != null && currentWorldUsersLabel != null) {
if (data.currentWorldCount > 0) {
currentWorldUsersLabel.setText("World " + data.currentWorldId + ": " + data.currentWorldCount + " players");
currentWorldUsersLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
} else {
currentWorldUsersLabel.setText("World " + data.currentWorldId + ": 0 players");
currentWorldUsersLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
}
} else if (currentWorldUsersLabel != null) {
currentWorldUsersLabel.setText("Current World: Not connected");
currentWorldUsersLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
}

// Update top world
if (topWorldLabel != null) {
if (data.topWorldId != null && data.topWorldCount > 0) {
topWorldLabel.setText("Top World " + data.topWorldId + ": " + data.topWorldCount + " players");
topWorldLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
} else {
topWorldLabel.setText("Top World: No data");
topWorldLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
}
}
});
}
}
} finally {
response.close();
}
}
});
}
// Removed unused user count update methods - UI is commented out and data not displayed

private void fetchConnectionStats() {
fetchConnectionStatsWithRetry(0);
}

// Cache connection stats to avoid frequent API calls
private volatile long lastConnectionStatsFetch = 0;
private static final long CONNECTION_STATS_CACHE_DURATION = 240000; // 4 minutes

private void fetchConnectionStatsWithRetry(int attemptCount) {
// Don't fetch if we have recent data
long now = System.currentTimeMillis();
if (now - lastConnectionStatsFetch < CONNECTION_STATS_CACHE_DURATION) {
log.debug("[STATS] Using cached connection stats, age: {} ms", (now - lastConnectionStatsFetch));
return;
}
// Skip if we don't have the required dependencies
if (httpClient == null || gson == null) {
return;
Expand All @@ -706,7 +607,7 @@ private void fetchConnectionStatsWithRetry(int attemptCount) {
httpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, java.io.IOException e) {
log.debug("Error fetching connection stats (attempt {}): {}", attemptCount + 1, e.getMessage());
log.debug("[STATS] Error fetching connection stats (attempt {}): {}", attemptCount + 1, e.getMessage());

// Retry on network failures with exponential backoff
if (attemptCount < 2) {
Expand All @@ -727,9 +628,10 @@ public void onResponse(okhttp3.Call call, Response response) throws java.io.IOEx
if (response.isSuccessful() && response.body() != null) {
String responseBody = response.body().string();
parseConnectionStatsResponse(responseBody);
log.debug("Successfully fetched connection stats on attempt {}", attemptCount + 1);
lastConnectionStatsFetch = System.currentTimeMillis();
log.debug("[STATS] Successfully fetched connection stats on attempt {}", attemptCount + 1);
} else {
log.debug("Failed to fetch connection stats: HTTP {} (attempt {})", response.code(), attemptCount + 1);
log.debug("[STATS] Failed to fetch connection stats: HTTP {} (attempt {})", response.code(), attemptCount + 1);

// Retry on HTTP errors (5xx server errors, but not 4xx client errors)
if (response.code() >= 500 && attemptCount < 2) {
Expand Down Expand Up @@ -843,16 +745,7 @@ private String getCurrentWorldId() {
return null;
}

private static class UserCountResponse {
@SerializedName("totalOnline")
public int totalOnline;

@SerializedName("worldCounts")
public Map<String, Integer> worldCounts;

@SerializedName("error")
public String error;
}
// Removed UserCountResponse class - no longer fetching user counts

private static class ConnectionStatsResponse {
@SerializedName("currentConnections")
Expand All @@ -871,21 +764,7 @@ private static class ConnectionStatsResponse {
public String error;
}

private static class UserCountData {
final int totalOnline;
final int currentWorldCount;
final String currentWorldId;
final String topWorldId;
final int topWorldCount;

UserCountData(int totalOnline, int currentWorldCount, String currentWorldId, String topWorldId, int topWorldCount) {
this.totalOnline = totalOnline;
this.currentWorldCount = currentWorldCount;
this.currentWorldId = currentWorldId;
this.topWorldId = topWorldId;
this.topWorldCount = topWorldCount;
}
}
// Removed UserCountData class - no longer needed

private JPanel createReadOnlyToggleSection() {
JPanel panel = new JPanel(new GridBagLayout());
Expand Down Expand Up @@ -1004,13 +883,10 @@ private void updateStatusLabel(boolean readOnly) {
}

public void refreshUserCounts() {
// updateUserCounts(); // Commented out - online users disabled
// No-op - user counts UI is disabled
}

public void cleanup() {
if (userCountUpdateTimer != null) {
userCountUpdateTimer.stop();
}
if (connectionStatusTimer != null) {
connectionStatusTimer.stop();
}
Expand Down Expand Up @@ -1043,8 +919,8 @@ private void startConnectionStatusUpdates() {
connectionStatusTimer = new Timer(2000, e -> updateConnectionStatus());
connectionStatusTimer.start();

// Update connection stats every 30 seconds (less frequent)
Timer connectionStatsTimer = new Timer(30000, e -> fetchConnectionStats());
// Update connection stats every 5 minutes (reduced from 30 seconds)
Timer connectionStatsTimer = new Timer(300000, e -> fetchConnectionStats());
connectionStatsTimer.start();
}
}
45 changes: 38 additions & 7 deletions src/main/java/com/globalchat/SupporterManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@
@Singleton
public class SupporterManager {
private static final String SUPPORTERS_URL = "https://global-chat-frontend.vercel.app/api/supporters";
private static final long REFRESH_INTERVAL_MINUTES = 10;
private static final long REFRESH_INTERVAL_MINUTES = 60; // Reduced from 10 to 60 minutes
private static final long RETRY_DELAY_SECONDS = 30;
private static final int MAX_RETRIES = 3;

private final Gson gson;
private final OkHttpClient httpClient;
private final ScheduledExecutorService scheduler;
private List<Supporter> supporters = new ArrayList<>();
private int totalSupport = 0;
private String lastUpdated = "";
private volatile List<Supporter> supporters = new ArrayList<>();
private volatile int totalSupport = 0;
private volatile String lastUpdated = "";
private volatile int consecutiveFailures = 0;
private volatile long lastFetchAttempt = 0;

@Inject
public SupporterManager(Gson gson, OkHttpClient httpClient) {
Expand Down Expand Up @@ -93,14 +97,39 @@ private String getTierIcon(Supporter supporter) {
}

private void fetchSupporters() {
// Rate limit: Don't fetch more than once per minute
long now = System.currentTimeMillis();
if (now - lastFetchAttempt < 60000) {
log.debug("[SUPPORTERS] Skipping fetch - too soon since last attempt");
return;
}
lastFetchAttempt = now;

// Exponential backoff if we've had failures
if (consecutiveFailures > 0) {
long backoffTime = Math.min(300000, RETRY_DELAY_SECONDS * 1000 * (long)Math.pow(2, consecutiveFailures - 1));
if (now - lastFetchAttempt < backoffTime) {
log.debug("[SUPPORTERS] In backoff period after {} failures", consecutiveFailures);
return;
}
}

log.debug("[SUPPORTERS] Fetching supporter list");

Request request = new Request.Builder()
.url(SUPPORTERS_URL)
.build();

httpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, java.io.IOException e) {
log.error("Error fetching supporters", e);
consecutiveFailures++;
log.debug("[SUPPORTERS] Error fetching supporters (failure #{})", consecutiveFailures, e);

// After MAX_RETRIES failures, back off for longer
if (consecutiveFailures >= MAX_RETRIES) {
log.debug("[SUPPORTERS] Max retries reached, will retry in next scheduled interval");
}
}

@Override
Expand All @@ -109,9 +138,11 @@ public void onResponse(okhttp3.Call call, Response response) throws java.io.IOEx
if (response.isSuccessful() && response.body() != null) {
String responseBody = response.body().string();
parseSupportersResponse(responseBody);
log.debug("Successfully fetched {} supporters", supporters.size());
consecutiveFailures = 0; // Reset on success
log.debug("[SUPPORTERS] Successfully fetched {} supporters", supporters.size());
} else {
log.debug("Failed to fetch supporters: HTTP {}", response.code());
consecutiveFailures++;
log.debug("[SUPPORTERS] Failed to fetch supporters: HTTP {} (failure #{})", response.code(), consecutiveFailures);
}
} finally {
response.close();
Expand Down