/*
* This file is part of mlDHT.
*
* mlDHT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* mlDHT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with mlDHT. If not, see <http://www.gnu.org/licenses/>.
*/
package lbms.plugins.mldht.kad.tasks;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import lbms.plugins.mldht.kad.DHT;
import lbms.plugins.mldht.kad.KBucket;
import lbms.plugins.mldht.kad.KBucketEntry;
import lbms.plugins.mldht.kad.Node;
import lbms.plugins.mldht.kad.RPCCall;
import lbms.plugins.mldht.kad.RPCServer;
import lbms.plugins.mldht.kad.messages.MessageBase;
import lbms.plugins.mldht.kad.messages.PingRequest;
/**
* @author Damokles
*
*/
public class PingRefreshTask extends Task {
private boolean cleanOnTimeout;
boolean alsoCheckGood = false;
boolean probeReplacement = false;
Deque<KBucketEntry> todo;
Set<KBucketEntry> visited;
private Map<MessageBase, KBucketEntry> lookupMap;
KBucket bucket;
/**
* @param rpc
* @param node
* @param bucket the bucket to refresh
* @param cleanOnTimeout if true Nodes that fail to respond are removed. should be false for normal use.
*/
public PingRefreshTask (RPCServer rpc, Node node, KBucket bucket, boolean cleanOnTimeout) {
super(rpc, node);
this.cleanOnTimeout = cleanOnTimeout;
todo = new ArrayDeque<>();
visited = new HashSet<>();
lookupMap = new HashMap<>();
addBucket(bucket);
}
public void checkGoodEntries(boolean val) {
alsoCheckGood = val;
}
public void probeUnverifiedReplacement(boolean val) {
probeReplacement = true;
}
public void addBucket(KBucket bucket) {
if (bucket != null) {
if(this.bucket !=null)
throw new IllegalStateException("a bucket already present");
this.bucket = bucket;
bucket.updateRefreshTimer();
for (KBucketEntry e : bucket.getEntries()) {
if (e.needsPing() || cleanOnTimeout || alsoCheckGood) {
todo.add(e);
}
}
if(probeReplacement) {
bucket.findPingableReplacement().ifPresent(todo::add);
}
}
}
/* (non-Javadoc)
* @see lbms.plugins.mldht.kad.Task#callFinished(lbms.plugins.mldht.kad.RPCCallBase, lbms.plugins.mldht.kad.messages.MessageBase)
*/
@Override
void callFinished (RPCCall c, MessageBase rsp) {
// most of the success handling is done by bucket maintenance
synchronized (lookupMap) {
KBucketEntry e = lookupMap.remove(c.getRequest());
}
}
/* (non-Javadoc)
* @see lbms.plugins.mldht.kad.Task#callTimeout(lbms.plugins.mldht.kad.RPCCallBase)
*/
@Override
void callTimeout (RPCCall c) {
MessageBase mb = c.getRequest();
synchronized (lookupMap) {
KBucketEntry e = lookupMap.remove(mb);
if(e == null)
return;
KBucket bucket = node.table().entryForId(e.getID()).getBucket();
if (bucket != null) {
if(cleanOnTimeout) {
DHT.logDebug("Removing invalid entry from cache.");
bucket.removeEntryIfBad(e, true);
}
}
}
}
@Override
public int getTodoCount() {
return todo.size();
}
@Override
void update () {
if(todo.isEmpty()) {
bucket.entriesStream().filter(KBucketEntry::needsPing).filter(e -> !lookupMap.values().contains(e)).forEach(todo::add);
}
while(!todo.isEmpty() && canDoRequest()) {
KBucketEntry e = todo.peekFirst();
if (visited.contains(e) || (!alsoCheckGood && !e.needsPing())) {
todo.remove(e);
continue;
}
PingRequest pr = new PingRequest();
pr.setDestination(e.getAddress());
if(!rpcCall(pr,e.getID(),c -> {
c.builtFromEntry(e);
synchronized (lookupMap) {
lookupMap.put(pr, e);
}
visited.add(e);
todo.remove(e);
})) {
break;
}
}
}
@Override
protected boolean isDone() {
return todo.isEmpty() && getNumOutstandingRequests() == 0 && !isFinished();
}
}