+package net.pterodactylus.xdcc.core;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import net.pterodactylus.xdcc.data.Network;
+
+/**
+ * Manages backoff times for connections.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ConnectionBackoff {
+
+ private static final long MILLIS_PER_SECOND = TimeUnit.MINUTES.toMillis(1);
+ private static final long MILLIS_PER_HOUR = TimeUnit.HOURS.toMillis(1);
+
+ private final Clock clock;
+ private final Map<Network, ConnectionFailureCounter> connectionFailures = new HashMap<>();
+
+ public ConnectionBackoff() {
+ this(Clock.systemDefaultZone());
+ }
+
+ public ConnectionBackoff(Clock clock) {
+ this.clock = clock;
+ }
+
+ public void connectionFailed(Network network) {
+ ConnectionFailureCounter connectionFailureCounter = getConnectionFailureCounter(network);
+ connectionFailureCounter.countFailure();
+ }
+
+ public void connectionSuccessful(Network network) {
+ ConnectionFailureCounter connectionFailureCounter = getConnectionFailureCounter(network);
+ connectionFailureCounter.reset();
+ }
+
+ public long getBackoff(Network network) {
+ ConnectionFailureCounter connectionFailureCounter = getConnectionFailureCounter(network);
+ if (!connectionFailureCounter.hasFailure()) {
+ return 0;
+ }
+ long delay = (long) (MILLIS_PER_SECOND *
+ Math.pow(1.2, connectionFailureCounter.getFailureCount() - 1));
+ return Math.min(MILLIS_PER_HOUR, Math.max(0,
+ (connectionFailureCounter.getLastFailureTime() + delay) - Instant.now(clock)
+ .toEpochMilli()));
+ }
+
+ private ConnectionFailureCounter getConnectionFailureCounter(Network network) {
+ ConnectionFailureCounter connectionFailureCounter;
+ synchronized (connectionFailures) {
+ if (!connectionFailures.containsKey(network)) {
+ connectionFailures.put(network, new ConnectionFailureCounter());
+ }
+ connectionFailureCounter = connectionFailures.get(network);
+ }
+ return connectionFailureCounter;
+ }
+
+ private class ConnectionFailureCounter {
+
+ private final Object lock = new Object();
+ private int count;
+ private long lastFailureTime;
+
+ public void reset() {
+ synchronized (lock) {
+ count = 0;
+ lastFailureTime = 0;
+ }
+ }
+
+ public void countFailure() {
+ synchronized (lock) {
+ count++;
+ lastFailureTime = Instant.now(clock).toEpochMilli();
+ }
+ }
+
+ public boolean hasFailure() {
+ synchronized (lock) {
+ return (count > 0);
+ }
+ }
+
+ public int getFailureCount() {
+ synchronized (lock) {
+ return count;
+ }
+ }
+
+ public long getLastFailureTime() {
+ synchronized (lock) {
+ return lastFailureTime;
+ }
+ }
+
+ }
+
+}