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

bt.tracker.MultiTracker Maven / Gradle / Ivy

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

import bt.BtException;
import bt.metainfo.TorrentId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

class MultiTracker implements Tracker {

    private static final Logger LOGGER = LoggerFactory.getLogger(MultiTracker.class);

    private ITrackerService trackerService;
    private List> trackerTiers;

    MultiTracker(ITrackerService trackerService, AnnounceKey announceKey) {
        this(trackerService, announceKey, true);
    }

    MultiTracker(ITrackerService trackerService, AnnounceKey announceKey, boolean shouldShuffleTiers) {
        if (!announceKey.isMultiKey()) {
            throw new IllegalArgumentException("Not a multi key: " + announceKey);
        }
        this.trackerService = trackerService;
        this.trackerTiers = initTrackers(announceKey, shouldShuffleTiers);
    }

    private List> initTrackers(AnnounceKey announceKey, boolean shouldShuffleTiers) {

        List> trackerUrls = announceKey.getTrackerUrls();
        List> trackers = new ArrayList<>(trackerUrls.size() + 1);

        for (List tier : trackerUrls) {
            List tierTrackers = new ArrayList<>(tier.size() + 1);
            for (String trackerUrl : tier) {
                tierTrackers.add(new LazyTracker(() -> trackerService.getTracker(trackerUrl)));
            }
            // per BEP-12 spec each tier must be shuffled
            if (shouldShuffleTiers) {
                Collections.shuffle(tierTrackers);
            }
            trackers.add(tierTrackers);
        }

        return trackers;
    }

    @Override
    public TrackerRequestBuilder request(TorrentId torrentId) {
        return new TrackerRequestBuilder(torrentId) {

            @Override
            public TrackerResponse start() {
                return tryForAllTrackers(tracker -> getDelegate(tracker, torrentId).start());
            }

            @Override
            public TrackerResponse stop() {
                return tryForAllTrackers(tracker -> getDelegate(tracker, torrentId).stop());
            }

            @Override
            public TrackerResponse complete() {
                return tryForAllTrackers(tracker -> getDelegate(tracker, torrentId).complete());
            }

            @Override
            public TrackerResponse query() {
                return tryForAllTrackers(tracker -> getDelegate(tracker, torrentId).query());
            }

            private TrackerRequestBuilder getDelegate(Tracker tracker, TorrentId torrentId) {
                TrackerRequestBuilder delegate = tracker.request(torrentId);

                int downloaded = getDownloaded();
                if (downloaded > 0) {
                    delegate.downloaded(downloaded);
                }

                int uploaded = getUploaded();
                if (uploaded > 0) {
                    delegate.uploaded(uploaded);
                }

                int left = getLeft();
                if (left > 0) {
                    delegate.left(left);
                }

                return delegate;
            }

            private TrackerResponse tryForAllTrackers(Function func) {

                List responses = new ArrayList<>();

                for (List trackerTier : trackerTiers) {

                    TrackerResponse response;
                    Tracker currentTracker;

                    for (int i = 0; i < trackerTier.size(); i++) {
                        currentTracker = trackerTier.get(i);
                        response = func.apply(currentTracker);
                        responses.add(response);

                        if (response.isSuccess()) {
                            if (trackerTier.size() > 1) {
                                trackerTier.remove(i);
                                trackerTier.add(0, currentTracker);
                            }
                            return response;
                        } else if (response.getError().isPresent()) {
                            Throwable e = response.getError().get();
                            LOGGER.warn("Unexpected error during interaction with the tracker: " + currentTracker, e);
                        } else {
                            LOGGER.warn("Unexpected error during interaction with the tracker: " + currentTracker +
                                    "; message: " + response.getErrorMessage());
                        }
                    }
                }

                throw new BtException("All trackers failed; responses (in chrono order): " + responses);
            }
        };
    }

    private static class LazyTracker implements Tracker {

        private volatile Tracker delegate;
        private Supplier delegateSupplier;
        private final Object lock;

        LazyTracker(Supplier delegateSupplier) {
            this.delegateSupplier = delegateSupplier;
            lock = new Object();
        }

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

        private Tracker getDelegate() {

            if (delegate == null) {
                synchronized (lock) {
                    if (delegate == null) {
                        delegate = delegateSupplier.get();
                    }
                }
            }
            return delegate;
        }

        @Override
        public String toString() {
            return getDelegate().toString();
        }
    }

    @Override
    public String toString() {
        return "MultiTracker{" +
                "trackerTiers=" + trackerTiers +
                '}';
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy