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

bt.it.fixture.SharedTrackerModule Maven / Gradle / Ivy

/*
 * Copyright (c) 2016—2021 Andrei Tomashpolskiy and individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package bt.it.fixture;

import bt.metainfo.TorrentId;
import bt.net.Peer;
import bt.peer.IPeerRegistry;
import bt.tracker.AnnounceKey;
import bt.tracker.ITrackerService;
import bt.tracker.Tracker;
import bt.tracker.TrackerRequestBuilder;
import bt.tracker.TrackerResponse;
import com.google.inject.Binder;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.Singleton;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Provides tracker, that is shared among all peers in the swarm.
 *
 * @since 1.0
 */
public class SharedTrackerModule implements Module {

    /**
     * Allows to filter the list of peers, that is returned by shared tracker to any given peer.
     *
     * @since 1.0
     */
    public interface PeerFilter {

        /**
         * Filter the list of peers, that will be returned to any given peer.
         *
         * @param self Some of the peers in the swarm
         * @param peers All peers in the swarm, that are known to the shared tracker
         * @return Filtered list of peers
         * @since 1.0
         */
        Collection filterPeers(Peer self, Set peers);
    }

    private PeerFilter peerFilter;

    /**
     * Create a shared tracker with default peer filter
     * (return the list of all peers except for the requesting peer).
     *
     * @since 1.0
     */
    public SharedTrackerModule() {
        this(new DefaultPeerFilter());
    }

    /**
     * Create a shared tracker with custom peer filter.
     *
     * @param peerFilter Custom peer filter
     * @since 1.0
     */
    public SharedTrackerModule(PeerFilter peerFilter) {
        this.peerFilter = peerFilter;
    }

    @Override
    public void configure(Binder binder) {
        binder.bind(ITrackerService.class).to(PeerTrackerService.class).in(Singleton.class);
    }

    @Provides
    @Singleton
    public PeerFilter providePeerFilter() {
        return peerFilter;
    }

    private static class DefaultPeerFilter implements PeerFilter {

        @Override
        public Collection filterPeers(Peer self, Set peers) {
            peers.remove(self);
            return peers;
        }
    }

    private static class PeerTrackerService implements ITrackerService {

        private final Provider peerRegistryProvider;
        private volatile IPeerRegistry peerRegistry;

        private PeerFilter peerFilter;
        private ConcurrentMap trackers;

        @Inject
        PeerTrackerService(Provider peerRegistryProvider, PeerFilter peerFilter) {
            this.peerRegistryProvider = peerRegistryProvider;
            this.peerFilter = peerFilter;
            this.trackers = new ConcurrentHashMap<>();
        }

        @Override
        public boolean isSupportedProtocol(String trackerUrl) {
            return true;
        }

        @Override
        public Tracker getTracker(String trackerUrl) {
            Tracker tracker = trackers.get(trackerUrl);
            if (tracker == null) {
                tracker = new Tracker() {
                    private TrackerRequestBuilder requestBuilder =
                            new TrackerRequestBuilder(TorrentId.fromBytes(new byte[TorrentId.length()])) {

                                @Override
                                public TrackerResponse start() {
                                    knownPeersService.addPeer(trackerUrl, getLocalPeer());
                                    return queryPeers();
                                }

                                @Override
                                public TrackerResponse stop() {
                                    knownPeersService.removePeer(trackerUrl, getLocalPeer());
                                    return TrackerResponse.ok();
                                }

                                @Override
                                public TrackerResponse complete() {
                                    return TrackerResponse.ok();
                                }

                                @Override
                                public TrackerResponse query() {
                                    return queryPeers();
                                }

                                private TrackerResponse queryPeers() {
                                    return new StartResponse(peerFilter.filterPeers(
                                            getLocalPeer(), knownPeersService.getPeersSnapshot(trackerUrl)));
                                }
                    };

                    @Override
                    public TrackerRequestBuilder request(TorrentId torrentId) {
                        return requestBuilder;
                    }

                    @Override
                    public void close() {
                        // do nothing
                    }
                };

                Tracker existing = trackers.putIfAbsent(trackerUrl, tracker);

                if (existing != null) {
                    tracker = existing;
                }
            }
            return tracker;
        }

        private Peer getLocalPeer() {
            if (peerRegistry == null) {
                synchronized (peerRegistryProvider) {
                    if (peerRegistry == null) {
                        peerRegistry = peerRegistryProvider.get();
                    }
                }
            }
            return peerRegistry.getLocalPeer();
        }

        @Override
        public Tracker getTracker(AnnounceKey announceKey) {
            if (announceKey.isMultiKey()) {
                throw new IllegalStateException("Multi keys not supported: " + announceKey);
            }
            return getTracker(announceKey.getTrackerUrl());
        }
    }

    private static KnownPeersService knownPeersService = new KnownPeersService();

    private static class KnownPeersService {

        private ConcurrentMap> knownPeers;
        private ReentrantReadWriteLock lock;

        KnownPeersService() {
            knownPeers = new ConcurrentHashMap<>();
            lock = new ReentrantReadWriteLock(true);
        }

        public Set getPeersSnapshot(String trackerUrl) {
            lock.readLock().lock();
            try {
                Set peers = knownPeers.get(trackerUrl);
                if (peers == null) {
                    return Collections.emptySet();
                }

                return new HashSet<>(peers);

            } finally {
                lock.readLock().unlock();
            }
        }

        public void addPeer(String trackerUrl, Peer peer) {
            lock.writeLock().lock();
            try {
                Set peers = knownPeers.get(trackerUrl);
                if (peers == null) {
                    peers = new HashSet<>();
                    knownPeers.put(trackerUrl, peers);
                }
                peers.add(peer);
            } finally {
                lock.writeLock().unlock();
            }
        }

        public void removePeer(String trackerUrl, Peer peer) {
            lock.writeLock().lock();
            try {
                Set peers = knownPeers.get(trackerUrl);
                if (peers == null) {
                    return;
                }
                peers.remove(peer);
            } finally {
                lock.writeLock().unlock();
            }
        }
    }

    private static class StartResponse extends TrackerResponse {

        private Collection peers;

        StartResponse(Collection peers) {
            this.peers = peers;
        }

        @Override
        public int getInterval() {
            return 0;
        }

        @Override
        public int getMinInterval() {
            return 0;
        }

        @Override
        public Iterable getPeers() {
            return peers;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy