All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bt.peer.PeerRegistry Maven / Gradle / Ivy

There is a newer version: 1.10
Show newest version
package bt.peer;

import bt.metainfo.Torrent;
import bt.metainfo.TorrentId;
import bt.net.InetPeer;
import bt.net.Peer;
import bt.net.PeerId;
import bt.service.IRuntimeLifecycleBinder;
import bt.service.IdentityService;
import bt.torrent.TorrentDescriptor;
import bt.torrent.TorrentRegistry;
import bt.tracker.AnnounceKey;
import bt.tracker.ITrackerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

/**
 *

Note that this class implements a service. * Hence, is not a part of the public API and is a subject to change.

*/ public class PeerRegistry implements IPeerRegistry { private static final Logger LOGGER = LoggerFactory.getLogger(PeerRegistry.class); private final Peer localPeer; private final PeerCache cache; private TorrentRegistry torrentRegistry; private ITrackerService trackerService; private TrackerPeerSourceFactory trackerPeerSourceFactory; private Set extraPeerSourceFactories; private ConcurrentMap>> peerConsumers; private ConcurrentMap> extraAnnounceKeys; private ReentrantLock extraAnnounceKeysLock; public PeerRegistry(IRuntimeLifecycleBinder lifecycleBinder, IdentityService idService, TorrentRegistry torrentRegistry, ITrackerService trackerService, Set extraPeerSourceFactories, InetAddress localPeerAddress, int localPeerPort, Duration peerDiscoveryInterval, Duration trackerQueryInterval) { this.peerConsumers = new ConcurrentHashMap<>(); this.localPeer = new InetPeer(localPeerAddress, localPeerPort, idService.getLocalPeerId()); this.cache = new PeerCache(); this.torrentRegistry = torrentRegistry; this.trackerService = trackerService; this.trackerPeerSourceFactory = new TrackerPeerSourceFactory(trackerService, torrentRegistry, lifecycleBinder, trackerQueryInterval); this.extraPeerSourceFactories = extraPeerSourceFactories; this.extraAnnounceKeys = new ConcurrentHashMap<>(); this.extraAnnounceKeysLock = new ReentrantLock(); createExecutor(lifecycleBinder, peerDiscoveryInterval); } private void createExecutor(IRuntimeLifecycleBinder lifecycleBinder, Duration peerDiscoveryInterval) { ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "bt.peer.peer-collector")); lifecycleBinder.onStartup("Schedule periodic peer lookup", () -> executor.scheduleAtFixedRate( this::collectAndVisitPeers, 1, peerDiscoveryInterval.toMillis(), TimeUnit.MILLISECONDS)); lifecycleBinder.onShutdown("Shutdown peer lookup scheduler", executor::shutdownNow); } private void collectAndVisitPeers() { peerConsumers.keySet().forEach(torrentId -> { Optional descriptor = torrentRegistry.getDescriptor(torrentId); if (descriptor.isPresent() && descriptor.get().isActive()) { Optional torrentOptional = torrentRegistry.getTorrent(torrentId); Optional torrentAnnounceKey = torrentOptional.isPresent() ? torrentOptional.get().getAnnounceKey() : Optional.empty(); Collection extraTorrentAnnounceKeys = extraAnnounceKeys.get(torrentId); if (extraTorrentAnnounceKeys == null) { queryTrackers(torrentId, torrentAnnounceKey, Collections.emptyList()); } else if (torrentOptional.isPresent() && torrentOptional.get().isPrivate()) { if (extraTorrentAnnounceKeys.size() > 0) { // prevent violating private torrents' rule of "only one tracker" LOGGER.warn("Will not query extra trackers for a private torrent, id: {}", torrentId); } } else { // more announce keys might be added at the same time; // querying all trackers can be time-consuming, so we make a copy of the collection // to prevent blocking callers of addPeerSource(TorrentId, AnnounceKey) for too long Collection extraTorrentAnnounceKeysCopy; extraAnnounceKeysLock.lock(); try { extraTorrentAnnounceKeysCopy = new ArrayList<>(extraTorrentAnnounceKeys); } finally { extraAnnounceKeysLock.unlock(); } queryTrackers(torrentId, torrentAnnounceKey, extraTorrentAnnounceKeysCopy); } // disallow querying peer sources other than the tracker for private torrents if ((!torrentOptional.isPresent() || !torrentOptional.get().isPrivate()) && !extraPeerSourceFactories.isEmpty()) { extraPeerSourceFactories.forEach(factory -> queryPeerSource(torrentId, factory.getPeerSource(torrentId))); } } }); } private void queryTrackers(TorrentId torrentId, Optional torrentAnnounceKey, Collection extraAnnounceKeys) { torrentAnnounceKey.ifPresent(announceKey -> { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Querying tracker peer source (announce key: {}) for torrent id: {}", announceKey, torrentId); } try { queryTracker(torrentId, announceKey); } catch (Exception e) { LOGGER.error("Error when querying tracker (torrent's announce key): " + announceKey, e); } }); extraAnnounceKeys.forEach(announceKey -> { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Querying tracker peer source (announce key: {}) for torrent id: {}", announceKey, torrentId); } try { queryTracker(torrentId, announceKey); } catch (Exception e) { LOGGER.error("Error when querying tracker (extra announce key): " + announceKey, e); } }); } private void queryTracker(TorrentId torrentId, AnnounceKey announceKey) { if (mightCreateTracker(announceKey)) { queryPeerSource(torrentId, trackerPeerSourceFactory.getPeerSource(torrentId, announceKey)); } } private boolean mightCreateTracker(AnnounceKey announceKey) { if (announceKey.isMultiKey()) { // TODO: need some more sophisticated solution because some of the trackers might be supported for (List tier : announceKey.getTrackerUrls()) { for (String trackerUrl : tier) { if (!trackerService.isSupportedProtocol(trackerUrl)) { return false; } } } return true; } else { return trackerService.isSupportedProtocol(announceKey.getTrackerUrl()); } } private void queryPeerSource(TorrentId torrentId, PeerSource peerSource) { try { if (peerSource.update()) { Collection discoveredPeers = peerSource.getPeers(); for (Peer peer : discoveredPeers) { addPeer(torrentId, peer); } } } catch (Exception e) { LOGGER.error("Error when querying peer source: " + peerSource, e); } } @Override public void addPeer(TorrentId torrentId, Peer peer) { if (isLocal(peer)) { return; } cache.registerPeer(peer); for (Consumer consumer : peerConsumers.getOrDefault(torrentId, Collections.emptyList())) { try { consumer.accept(peer); } catch (Exception e) { LOGGER.error("Error in peer consumer", e); } } } @Override public void addPeerSource(TorrentId torrentId, AnnounceKey announceKey) { extraAnnounceKeysLock.lock(); try { getOrCreateExtraAnnounceKeys(torrentId).add(announceKey); } finally { extraAnnounceKeysLock.unlock(); } } private Set getOrCreateExtraAnnounceKeys(TorrentId torrentId) { Set announceKeys = extraAnnounceKeys.get(torrentId); if (announceKeys == null) { announceKeys = ConcurrentHashMap.newKeySet(); Set existing = extraAnnounceKeys.putIfAbsent(torrentId, announceKeys); if (existing != null) { announceKeys = existing; } } return announceKeys; } private boolean isLocal(Peer peer) { return peer.getInetAddress().isAnyLocalAddress() && localPeer.getPort() == peer.getPort(); } @Override public Peer getLocalPeer() { return localPeer; } @Override public Peer getPeerForAddress(InetSocketAddress address) { return cache.getPeerForAddress(address); } @Override public void addPeerConsumer(Torrent torrent, Consumer consumer) { addPeerConsumer(torrent.getTorrentId(), consumer); } @Override public void addPeerConsumer(TorrentId torrentId, Consumer consumer) { List> consumers = peerConsumers.get(torrentId); if (consumers == null) { consumers = new ArrayList<>(); List> existing = peerConsumers.putIfAbsent(torrentId, consumers); if (existing != null) { consumers = existing; } } consumers.add(consumer); } // TODO: someone should call this after torrent is stopped/completed @Override public void removePeerConsumers(Torrent torrent) { removePeerConsumers(torrent.getTorrentId()); } @Override public void removePeerConsumers(TorrentId torrentId) { peerConsumers.remove(torrentId); } private static class PeerCache { // all known peers (lookup by inet address) private final ConcurrentMap knownPeers; private final ReentrantLock peerLock; PeerCache() { this.knownPeers = new ConcurrentHashMap<>(); this.peerLock = new ReentrantLock(); } // need to do this atomically: // - concurrent call to getPeerForAddress(InetSocketAddress) // might coincide with querying peer sources (overwriting options, etc) private UpdatablePeer registerPeer(Peer peer) { peerLock.lock(); try { UpdatablePeer newPeer = new UpdatablePeer(peer); UpdatablePeer existing = knownPeers.putIfAbsent(peer.getInetSocketAddress(), newPeer); if (existing != null) { existing.setOptions(peer.getOptions()); } return (existing == null) ? newPeer : existing; } finally { peerLock.unlock(); } } public Peer getPeerForAddress(InetSocketAddress address) { Peer existing = knownPeers.get(address); if (existing == null) { peerLock.lock(); try { existing = knownPeers.get(address); if (existing == null) { existing = registerPeer(new InetPeer(address)); } } finally { peerLock.unlock(); } } return existing; } } private static class UpdatablePeer implements Peer { private final Peer delegate; private volatile PeerOptions options; UpdatablePeer(Peer delegate) { super(); this.delegate = delegate; this.options = delegate.getOptions(); } @Override public InetSocketAddress getInetSocketAddress() { return delegate.getInetSocketAddress(); } @Override public InetAddress getInetAddress() { return delegate.getInetAddress(); } @Override public int getPort() { return delegate.getPort(); } @Override public Optional getPeerId() { return delegate.getPeerId(); } @Override public PeerOptions getOptions() { return options; } void setOptions(PeerOptions options) { this.options = options; } @Override public int hashCode() { return delegate.hashCode(); } @Override public boolean equals(Object object) { return delegate.equals(object); } @Override public String toString() { return delegate.toString(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy