ga.rugal.jpt.common.tracker.server.TrackedTorrent.java Source code

Java tutorial

Introduction

Here is the source code for ga.rugal.jpt.common.tracker.server.TrackedTorrent.java

Source

/**
 * Copyright (C) 2011-2012 Turn, Inc.
 *
 * 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 ga.rugal.jpt.common.tracker.server;

import ga.rugal.jpt.common.CommonLogContent;
import ga.rugal.jpt.common.CommonMessageContent;
import config.SystemDefaultProperties;
import ga.rugal.jpt.common.tracker.common.Peer;
import ga.rugal.jpt.common.tracker.common.Torrent;
import ga.rugal.jpt.common.tracker.common.TrackerUpdateBean;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Tracked torrents are torrent for which we don't expect to have data files
 * for.
 *
 * <p>
 * {@link TrackedTorrent} objects are used by the BitTorrent tracker to
 * represent a torrent that is announced by the tracker. As such, it is not
 * expected to point to any valid local data like. It also contains some
 * additional information used by the tracker to keep track of which peers
 * exchange on it, etc.
 * </p>
 *
 * @author mpetazzoni
 */
public class TrackedTorrent extends Torrent {

    private static final Logger LOG = LoggerFactory.getLogger(TrackedTorrent.class);

    private int answerPeers;

    private int announceInterval;

    /**
     * Peers currently exchanging on this torrent.
     */
    private ConcurrentMap<String, TrackedPeer> peers;

    /**
     * Create a new tracked torrent from meta-info binary data.
     *
     * @param torrent The meta-info byte data.
     *
     * @throws IOException When the info dictionary can't be
     *                     encoded and hashed back to create the torrent's SHA-1 hash.
     */
    public TrackedTorrent(byte[] torrent) throws IOException {
        super(torrent, false);
        this.peers = new ConcurrentHashMap<>();
        this.answerPeers = SystemDefaultProperties.DEFAULT_ANSWER_NUM_PEERS;
        this.announceInterval = SystemDefaultProperties.DEFAULT_ANNOUNCE_INTERVAL_SECONDS;
    }

    public TrackedTorrent(Torrent torrent) throws IOException {
        this(torrent.getEncoded());
    }

    /**
     * Returns the map of all peers currently exchanging on this torrent.
     *
     * @return
     */
    public Map<String, TrackedPeer> getPeers() {
        return this.peers;
    }

    /**
     * Add a peer exchanging on this torrent.
     *
     * @param peer The new Peer involved with this torrent.
     */
    public void addPeer(TrackedPeer peer) {
        this.peers.put(peer.getPeerId(), peer);
    }

    /**
     * Retrieve a peer exchanging on this torrent.
     *
     * @param peerId The hexadecimal representation of the peer's ID.
     *
     * @return
     */
    public TrackedPeer getPeer(String peerId) {
        return this.peers.get(peerId);
    }

    public boolean containsKey(String peerId) {
        return peers.containsKey(peerId);
    }

    /**
     * Remove a peer from this torrent's swarm.
     *
     * @param peerId The hexadecimal representation of the peer's ID.
     *
     * @return
     */
    public TrackedPeer removePeer(String peerId) {
        return this.peers.remove(peerId);
    }

    /**
     * Count the number of seeders (peers in the COMPLETED state) on this
     * torrent.
     *
     * @return
     */
    public int seeders() {
        int count = 0;
        for (TrackedPeer peer : this.peers.values()) {
            if (peer.isCompleted()) {
                count++;
            }
        }
        return count;
    }

    /**
     * Count the number of leechers (non-COMPLETED peers) on this torrent.
     *
     * @return
     */
    public int leechers() {
        int count = 0;
        for (TrackedPeer peer : this.peers.values()) {
            if (!peer.isCompleted()) {
                count++;
            }
        }
        return count;
    }

    /**
     * Remove unfresh peers from this torrent.
     *
     * <p>
     * Collect and remove all non-fresh peers from this torrent. This is
     * usually called by the periodic peer collector of the BitTorrent tracker.
     * </p>
     */
    public void collectUnfreshPeers() {
        for (TrackedPeer peer : this.peers.values()) {
            if (!peer.isFresh()) {
                this.peers.remove(peer.getHexPeerId());
            }
        }
    }

    /**
     * Get the announce interval for this torrent.
     *
     * @return
     */
    public int getAnnounceInterval() {
        return this.announceInterval;
    }

    /**
     * Set the announce interval for this torrent.
     *
     * @param interval New announce interval, in seconds.
     */
    public void setAnnounceInterval(int interval) {
        if (interval <= 0) {
            throw new IllegalArgumentException("Invalid announce interval");
        }

        this.announceInterval = interval;
    }

    /**
     * Update this torrent's swarm from an announce event.
     * <p>
     * Either create new peer in tracked list or get it if already existed.
     * <p>
     * Then update the status of the tracked peer for future usage.
     * <p>
     * This will automatically create a new peer on a 'started' announce event,
     * and remove the peer on a 'stopped' announce event.
     * </p>
     *
     * @param bean
     *
     * @return The peer that sent us the announce request.
     *
     */
    public TrackedPeer update(TrackerUpdateBean bean) {
        TrackedPeer peer;
        bean.setState(TrackedPeer.PeerState.UNKNOWN);
        switch (bean.getEvent()) {
        case STARTED:
            peer = new TrackedPeer(this, bean.getIp(), bean.getPort(), bean.getBufferPeerID());
            bean.setState(TrackedPeer.PeerState.STARTED);
            this.addPeer(peer);
            break;
        case STOPPED:
            peer = this.removePeer(bean.getPeerID());
            bean.setState(TrackedPeer.PeerState.STOPPED);
            break;
        case COMPLETED:
            peer = this.getPeer(bean.getPeerID());
            bean.setState(TrackedPeer.PeerState.COMPLETED);
            break;
        case NONE:
            peer = this.getPeer(bean.getPeerID());
            bean.setState(TrackedPeer.PeerState.STARTED);
            break;
        default:
            throw new IllegalArgumentException(CommonMessageContent.BAD_EVENT);
        }
        LOG.debug(MessageFormat.format(CommonLogContent.UPDATE_CONTENT, bean.getUser().getUid(), bean.getInfoHash(),
                bean.getDownloaded(), bean.getUploaded(), bean.getLeft(), bean.getIp()));
        peer.update(bean);
        return peer;
    }

    /**
     * Get a list of peers we can return in an announce response for this
     * torrent.
     *
     * @param peer The peer making the request, so we can exclude it from the
     *             list of returned peers.
     *
     * @return A list of peers we can include in an announce response.
     */
    public List<Peer> getSomePeers(TrackedPeer peer) {
        List<Peer> peers = new LinkedList<>();

        List<TrackedPeer> candidates;
        candidates = new LinkedList<>(this.peers.values());
        Collections.shuffle(candidates);

        int count = 0;
        for (TrackedPeer candidate : candidates) {
            // Collect unfresh peers, and obviously don't serve them as well.
            if (!candidate.isFresh() || (candidate.looksLike(peer) && !candidate.equals(peer))) {
                LOG.debug(CommonLogContent.STALE_PEERS, candidate);
                this.peers.remove(candidate.getHexPeerId());
                continue;
            }
            // Don't include the requesting peer in the answer.
            if (peer.looksLike(candidate)) {
                continue;
            }
            // Collect unfresh peers, and obviously don't serve them as well.
            if (!candidate.isFresh()) {
                LOG.debug(CommonLogContent.STALE_PEERS, candidate.getHexPeerId());
                this.peers.remove(candidate.getHexPeerId());
                continue;
            }
            // Only serve at most ANSWER_NUM_PEERS peers
            if (count++ > this.answerPeers) {
                break;
            }
            peers.add(candidate);
        }
        return peers;
    }

    /**
     * Load a tracked torrent from the given torrent file.
     *
     * @param file The abstract {@link File} object representing the
     * <tt>.torrent</tt> file to load.
     *
     * @return
     *
     * @throws IOException When the torrent file cannot be read.
     */
    public static TrackedTorrent load(File file) throws IOException {
        byte[] data = FileUtils.readFileToByteArray(file);
        TrackedTorrent torrent = new TrackedTorrent(data);
        return torrent;
    }

    /**
     * Load a tracked torrent from the given byte array.
     *
     * @param data
     *
     * @return
     *
     * @throws IOException When the torrent file cannot be read.
     */
    public static TrackedTorrent load(byte[] data) throws IOException {
        TrackedTorrent torrent = new TrackedTorrent(data);
        return torrent;
    }
}