package tc.oc.commons.bungee.restart; import java.time.Instant; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.inject.Inject; import com.google.common.eventbus.Subscribe; import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; import tc.oc.api.docs.Server; import tc.oc.api.docs.virtual.ServerDoc; import tc.oc.api.minecraft.servers.LocalServerReconfigureEvent; import tc.oc.api.minecraft.users.OnlinePlayers; import tc.oc.commons.core.logging.Loggers; import tc.oc.commons.core.plugin.PluginFacet; import tc.oc.commons.core.restart.CancelRestartEvent; import tc.oc.commons.core.restart.RequestRestartEvent; import tc.oc.commons.core.restart.RestartConfiguration; import tc.oc.commons.core.scheduler.ReusableTask; import tc.oc.commons.core.scheduler.Scheduler; import tc.oc.commons.core.scheduler.Task; public class RestartListener implements PluginFacet, Listener, Runnable { private final Logger logger; private final RestartConfiguration config; private final Server localServer; private final OnlinePlayers onlinePlayers; private final ReusableTask task; private @Nullable RequestRestartEvent.Deferral deferral; @Inject RestartListener(Loggers loggers, RestartConfiguration config, Server localServer, OnlinePlayers onlinePlayers, Scheduler scheduler) { this.logger = loggers.get(getClass()); this.config = config; this.localServer = localServer; this.onlinePlayers = onlinePlayers; this.task = scheduler.createReusableTask(this); } private boolean canRestart(int playerCount) { // If server is on public DNS, it cannot restart if(localServer.dns_enabled()) { logger.info("Deferring restart because server is on public DNS"); return false; } // If we are still waiting for the server to empty, it cannot restart final Instant deadline = localServer.dns_toggled_at().plus(config.emptyTimeout()); if(playerCount > 0 && deadline.isAfter(Instant.now())) { logger.info("Deferring restart until the server empties or until " + deadline + ", whichever is first"); task.schedule(Task.Parameters.fromInstant(deadline)); return false; } // If we have given up waiting for the server to empty, it can restart. // However, the kick-limit enforced by RestartManager may still prevent it from restarting. return true; } private void update(int playerCount) { if(this.deferral != null && canRestart(playerCount)) { final RequestRestartEvent.Deferral deferral = this.deferral; this.deferral = null; deferral.resume(); } } @Override public void run() { update(onlinePlayers.count()); } @Subscribe public void onRequestRestart(RequestRestartEvent event) { if(event.priority() < ServerDoc.Restart.Priority.HIGH && !canRestart(onlinePlayers.count())) { deferral = event.defer(getClass().getName()); } } @Subscribe public void onCancelRestart(CancelRestartEvent event) { deferral = null; } @Subscribe public void onReconfigure(LocalServerReconfigureEvent event) { if(event.getOldConfig() != null && event.getOldConfig().dns_enabled() && !event.getNewConfig().dns_enabled()) run(); } @EventHandler synchronized public void onConnect(final LoginEvent event) { this.update(onlinePlayers.count() + 1); } @EventHandler synchronized public void onDisconnect(final PlayerDisconnectEvent event) { this.update(onlinePlayers.count() - 1); } }