package net.buycraft.plugin.execution; import com.google.common.collect.ImmutableList; import lombok.RequiredArgsConstructor; import net.buycraft.plugin.IBuycraftPlatform; import net.buycraft.plugin.client.ApiException; import net.buycraft.plugin.data.QueuedPlayer; import net.buycraft.plugin.data.responses.DueQueueInformation; import java.io.IOException; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; @RequiredArgsConstructor public class DuePlayerFetcher implements Runnable { private static final int MAXIMUM_PER_PAGE = 250; private static final int FALLBACK_CHECK_BACK_SECS = 300; private static final int MAXIMUM_ONLINE_PLAYERS_TO_EXECUTE = 60; private static final int DELAY_BETWEEN_PLAYERS = 500; private final IBuycraftPlatform platform; private final Map<String, QueuedPlayer> due = new HashMap<>(); private final Lock lock = new ReentrantLock(); private final AtomicBoolean inProgress = new AtomicBoolean(false); private final boolean verbose; private final Random random = new Random(); public boolean inProgress() { return inProgress.get(); } @Override public void run() { run(true); } public void run(boolean scheduleAgain) { if (platform.getApiClient() == null) { return; // no API client } if (!inProgress.compareAndSet(false, true)) { platform.log(Level.INFO, "Already fetching due player information!"); return; } int nextCheck = FALLBACK_CHECK_BACK_SECS; try { if (verbose) { platform.log(Level.INFO, "Fetching all due players..."); } Map<String, QueuedPlayer> allDue = new HashMap<>(); DueQueueInformation information; int page = 1; do { try { information = platform.getApiClient().retrieveDueQueue(MAXIMUM_PER_PAGE, page); nextCheck = information.getMeta().getNextCheck(); } catch (IOException | ApiException e) { platform.log(Level.SEVERE, "Could not fetch due players queue", e); return; } for (QueuedPlayer player : information.getPlayers()) { // Using Locale.US as servers can sometimes have other locales in use. allDue.put(player.getName().toLowerCase(Locale.US), player); } if (page > 1) { try { Thread.sleep(random.nextInt(1000) + 500); } catch (InterruptedException e) { platform.log(Level.SEVERE, "Interrupted", e); } } page++; } while (information.getMeta().isMore()); if (verbose) { platform.log(Level.INFO, String.format("Fetched due players (%d found).", allDue.size())); } // Issue immediate task if required. if (information.getMeta().isExecuteOffline()) { if (verbose) { platform.log(Level.INFO, "Executing commands that can be completed now..."); } platform.executeAsync(new ImmediateCommandExecutor(platform)); } lock.lock(); try { due.clear(); due.putAll(allDue); } finally { lock.unlock(); } processOnlinePlayers(); } finally { inProgress.set(false); if (scheduleAgain) platform.executeAsyncLater(this, nextCheck, TimeUnit.SECONDS); } } private void processOnlinePlayers() { // Check for online players and execute their commands. List<QueuedPlayer> processNow = new ArrayList<>(); lock.lock(); try { for (Iterator<QueuedPlayer> it = due.values().iterator(); it.hasNext(); ) { QueuedPlayer qp = it.next(); if (platform.isPlayerOnline(qp)) { if (processNow.size() < MAXIMUM_ONLINE_PLAYERS_TO_EXECUTE) { processNow.add(qp); it.remove(); } } } } finally { lock.unlock(); } if (!processNow.isEmpty()) { if (verbose) { platform.log(Level.INFO, String.format("Executing commands for %d online players...", processNow.size())); } for (int i = 0; i < processNow.size(); i++) { QueuedPlayer qp = processNow.get(i); platform.executeAsyncLater(new PlayerCommandExecutor(qp, platform), DELAY_BETWEEN_PLAYERS * (i + 1), TimeUnit.MILLISECONDS); } } } public Collection<QueuedPlayer> getDuePlayers() { lock.lock(); try { return ImmutableList.copyOf(due.values()); } finally { lock.unlock(); } } public QueuedPlayer fetchAndRemoveDuePlayer(String name) { lock.lock(); try { // Using Locale.US as servers can sometimes have other locales in use. return due.remove(name.toLowerCase(Locale.US)); } finally { lock.unlock(); } } }