mirror of
https://github.com/Rogiel/torrent4j
synced 2025-12-06 07:32:47 +00:00
Initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/.project
|
||||
/.classpath
|
||||
/.settings
|
||||
208
pom.xml
Normal file
208
pom.xml
Normal file
@@ -0,0 +1,208 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.rogiel</groupId>
|
||||
<artifactId>torrent4j</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>torrent4j</name>
|
||||
<description>Bittorrent java library</description>
|
||||
|
||||
<inceptionYear>2012</inceptionYear>
|
||||
<organization>
|
||||
<name>Rogiel</name>
|
||||
<url>http://www.rogiel.com/</url>
|
||||
</organization>
|
||||
<url>http://www.rogiel.com/</url>
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:${basedir}</developerConnection>
|
||||
<connection>scm:git:${basedir}</connection>
|
||||
</scm>
|
||||
<distributionManagement>
|
||||
<downloadUrl>https://github.com/torrent4j/torrent4j/downloads</downloadUrl>
|
||||
<repository>
|
||||
<id>torrent4j-repository</id>
|
||||
<name>torrent4j-repository</name>
|
||||
<url>scp://rogiels@rogiel.com/home/rogiels/maven.rogiel.com</url>
|
||||
<layout>default</layout>
|
||||
<uniqueVersion>false</uniqueVersion>
|
||||
</repository>
|
||||
<site>
|
||||
<id>torrent4j-site</id>
|
||||
<name>torrent4j-site</name>
|
||||
<url>scp://rogiels@rogiel.com/home/rogiels/torrent4j.rogiel.com</url>
|
||||
</site>
|
||||
</distributionManagement>
|
||||
|
||||
<issueManagement>
|
||||
<system>GitHub</system>
|
||||
<url>https://github.com/torrent4j/torrent4j/issues</url>
|
||||
</issueManagement>
|
||||
|
||||
<ciManagement>
|
||||
<system>GitHub</system>
|
||||
<url>https://github.com/torrent4j/torrent4j</url>
|
||||
</ciManagement>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>Rogiel</id>
|
||||
<name>Rogiel</name>
|
||||
<email>rogiel@rogiel.com</email>
|
||||
<url>http://www.rogiel.com/</url>
|
||||
<timezone>-3</timezone>
|
||||
<roles>
|
||||
<role>Creator</role>
|
||||
<role>API Designer</role>
|
||||
</roles>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>GNU General Public License version 3</name>
|
||||
<url>http://www.gnu.org/licenses/gpl.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<build>
|
||||
<defaultGoal>package</defaultGoal>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>attached</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/distribution-bin.xml</descriptor>
|
||||
<descriptor>src/main/assembly/distribution-src.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.7</version>
|
||||
<configuration>
|
||||
<uniqueVersion>false</uniqueVersion>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.wagon</groupId>
|
||||
<artifactId>wagon-ssh</artifactId>
|
||||
<version>2.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-release-plugin</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<version>3.0</version>
|
||||
<configuration>
|
||||
<outputEncoding>UTF-8</outputEncoding>
|
||||
<reportPlugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.8</version>
|
||||
<configuration>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<developerConnection>scm:git@github.com:torrent4j/torrent4j.git</developerConnection>
|
||||
<anonymousConnection>scm:git://github.com/torrent4j/torrent4j.git</anonymousConnection>
|
||||
<webAccessUrl>https://github.com/torrent4j/torrent4j/tree/master/</webAccessUrl>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-changelog-plugin</artifactId>
|
||||
<version>2.2</version>
|
||||
<configuration>
|
||||
<outputEncoding>UTF-8</outputEncoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jxr-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<configuration>
|
||||
<inputEncoding>UTF-8</inputEncoding>
|
||||
<outputEncoding>UTF-8</outputEncoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</reportPlugins>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.wagon</groupId>
|
||||
<artifactId>wagon-ssh</artifactId>
|
||||
<version>2.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.10</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty</artifactId>
|
||||
<version>4.0.0.Alpha1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>20041127.091804</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jboss-snapshots</id>
|
||||
<url>https://repository.jboss.org/nexus/content/repositories/snapshots/</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
||||
25
src/main/assembly/distribution-bin.xml
Normal file
25
src/main/assembly/distribution-bin.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
|
||||
<id>bin</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<files>
|
||||
<file>
|
||||
<source>${project.build.directory}/${project.artifactId}-${project.version}.jar</source>
|
||||
<outputDirectory>/</outputDirectory>
|
||||
<destName>torrent4j-${project.version}.jar</destName>
|
||||
<fileMode>0755</fileMode>
|
||||
</file>
|
||||
</files>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<outputDirectory>/libs</outputDirectory>
|
||||
<useProjectArtifact>false</useProjectArtifact>
|
||||
<unpack>false</unpack>
|
||||
<scope>runtime</scope>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
||||
26
src/main/assembly/distribution-src.xml
Normal file
26
src/main/assembly/distribution-src.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
|
||||
<id>src</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}</directory>
|
||||
<outputDirectory>/</outputDirectory>
|
||||
<includes>
|
||||
<include>src/**</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<outputDirectory>/libs</outputDirectory>
|
||||
<useProjectArtifact>false</useProjectArtifact>
|
||||
<unpack>false</unpack>
|
||||
<scope>runtime</scope>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
||||
184
src/main/java/com/torrent4j/TorrentController.java
Normal file
184
src/main/java/com/torrent4j/TorrentController.java
Normal file
@@ -0,0 +1,184 @@
|
||||
package com.torrent4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
import com.torrent4j.net.TorrentProtocol;
|
||||
import com.torrent4j.net.peerwire.PeerWireProtocol;
|
||||
import com.torrent4j.storage.NIOTorrentStorage;
|
||||
import com.torrent4j.storage.TorrentStorage;
|
||||
import com.torrent4j.util.Hash;
|
||||
|
||||
/**
|
||||
* The main class for starting torrent transfers. Each torrent instance need to
|
||||
* be registered into an controller. Once registered, connections can be
|
||||
* established to start downloading the file (or files) from other peers in the
|
||||
* BitTorrent network.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class TorrentController {
|
||||
/**
|
||||
* The controller configuration
|
||||
*/
|
||||
private final TorrentControllerConfiguration config = new TorrentControllerConfiguration();
|
||||
/**
|
||||
* The torrent protocol
|
||||
*/
|
||||
private final TorrentProtocol protocol;
|
||||
/**
|
||||
* The torrent storage
|
||||
*/
|
||||
private final TorrentStorage storage;
|
||||
/**
|
||||
* The list of torrents controlled on this controller
|
||||
*/
|
||||
private final List<Torrent> torrents = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new controller
|
||||
*
|
||||
* @param protocol
|
||||
* the protocol to use
|
||||
* @param storage
|
||||
* the storage to use
|
||||
*/
|
||||
public TorrentController(TorrentProtocol protocol, TorrentStorage storage) {
|
||||
this.protocol = protocol;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new controller with {@link NIOTorrentStorage} as default
|
||||
* storage.
|
||||
*
|
||||
* @param protocol
|
||||
* thw protocol to use
|
||||
*/
|
||||
public TorrentController(TorrentProtocol protocol) {
|
||||
this(protocol, new NIOTorrentStorage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new controller with {@link PeerWireProtocol} as default
|
||||
* protocol.
|
||||
*
|
||||
* @param storage
|
||||
* the storage to use
|
||||
*/
|
||||
public TorrentController(TorrentStorage storage) {
|
||||
this(new PeerWireProtocol(), storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new controller with {@link PeerWireProtocol} as default
|
||||
* protocol and {@link NIOTorrentStorage} as default storage.
|
||||
*/
|
||||
public TorrentController() {
|
||||
this(new PeerWireProtocol(), new NIOTorrentStorage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves data from the storage and validate all piece information
|
||||
* already downloaded, if any. Automatically updates the torrent internal
|
||||
* state to match the real download progress.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent to check existing data
|
||||
* @throws IOException
|
||||
* if any IO error occur
|
||||
*/
|
||||
public void checkExistingData(Torrent torrent) throws IOException {
|
||||
for (final TorrentPiece piece : torrent.getPieces()) {
|
||||
final Hash hash = storage.checksum(piece);
|
||||
if (!piece.getHash().equals(hash))
|
||||
continue;
|
||||
torrent.getCompletedPieces().addPiece(piece);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new torrent on this controller
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent to be registered
|
||||
*/
|
||||
public void registerTorrent(Torrent torrent) {
|
||||
torrents.add(torrent);
|
||||
torrent.setController(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an already registered torrent from this controller
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent to be removed
|
||||
*/
|
||||
public void removeTorrent(Torrent torrent) {
|
||||
torrents.remove(torrent);
|
||||
torrent.setController(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to locate the torrent represented by <code>hash</code>.
|
||||
*
|
||||
* @param hash
|
||||
* the torrent hash to look for
|
||||
* @return the torrent with the given <code>hash</code>, if any
|
||||
*/
|
||||
public Torrent findTorrent(Hash hash) {
|
||||
for (final Torrent torrent : torrents) {
|
||||
if (torrent.getHash().equals(hash))
|
||||
return torrent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stars the controller
|
||||
*
|
||||
* @param port
|
||||
* the listen port
|
||||
*/
|
||||
public void start(int port) {
|
||||
protocol.start(this, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the controller
|
||||
*/
|
||||
public void stop() {
|
||||
protocol.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this controller configuration object
|
||||
*/
|
||||
public TorrentControllerConfiguration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this controller protocol
|
||||
*/
|
||||
public TorrentProtocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this controller storage
|
||||
*/
|
||||
public TorrentStorage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this controller torrent list
|
||||
*/
|
||||
public List<Torrent> getTorrents() {
|
||||
return torrents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.torrent4j;
|
||||
|
||||
import com.torrent4j.util.PeerIDGenerator;
|
||||
|
||||
public class TorrentControllerConfiguration {
|
||||
private String peerID = PeerIDGenerator.generateRandomPeerID();
|
||||
|
||||
public String getPeerID() {
|
||||
return peerID;
|
||||
}
|
||||
|
||||
public void setPeerID(String peerID) {
|
||||
this.peerID = peerID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.util.BitField;
|
||||
|
||||
public abstract class AbstractTorrentPiecesContainer implements Iterable<TorrentPiece> {
|
||||
protected final Torrent torrent;
|
||||
protected final BitField bitSet;
|
||||
|
||||
public AbstractTorrentPiecesContainer(Torrent torrent) {
|
||||
this.torrent = torrent;
|
||||
this.bitSet = new BitField(torrent.getPieces().size());
|
||||
}
|
||||
|
||||
public boolean hasPiece(TorrentPiece piece) {
|
||||
return bitSet.get(piece.getIndex());
|
||||
}
|
||||
|
||||
public void addPiece(TorrentPiece piece) {
|
||||
bitSet.set(piece.getIndex(), true);
|
||||
}
|
||||
|
||||
public void removePiece(TorrentPiece piece) {
|
||||
bitSet.set(piece.getIndex(), false);
|
||||
}
|
||||
|
||||
public boolean isSeeder() {
|
||||
return bitSet.cardinality() == torrent.getPieces().size();
|
||||
}
|
||||
|
||||
public boolean isLeecher() {
|
||||
return !isSeeder();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
bitSet.clear();
|
||||
}
|
||||
|
||||
public List<TorrentPiece> getPieces() {
|
||||
return createList(bitSet);
|
||||
}
|
||||
|
||||
public boolean hasMissingPieces(AbstractTorrentPiecesContainer other) {
|
||||
final BitSet difference = (BitSet) bitSet.clone();
|
||||
difference.andNot(other.bitSet);
|
||||
return difference.cardinality() != 0;
|
||||
}
|
||||
|
||||
public List<TorrentPiece> getMissingPieces(AbstractTorrentPiecesContainer other) {
|
||||
final BitSet difference = (BitSet) bitSet.clone();
|
||||
difference.andNot(other.bitSet);
|
||||
return createList(difference);
|
||||
}
|
||||
|
||||
public List<TorrentPiece> createList(BitSet bits) {
|
||||
final List<TorrentPiece> list = new ArrayList<>();
|
||||
for (int i = bits.nextSetBit(0); i >= 0; i = bits.nextSetBit(i + 1)) {
|
||||
list.add(getTorrent().getPiece(i));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<TorrentPiece> iterator() {
|
||||
return new Iterator<TorrentPiece>() {
|
||||
private int lastIndex = 0;
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return bitSet.nextSetBit(lastIndex) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentPiece next() {
|
||||
lastIndex = index;
|
||||
return getTorrent().getPiece(
|
||||
(index = bitSet.nextSetBit(lastIndex)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
removePiece(getTorrent().getPiece(lastIndex));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public BitField getBitSet() {
|
||||
return bitSet;
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
}
|
||||
359
src/main/java/com/torrent4j/model/Torrent.java
Normal file
359
src/main/java/com/torrent4j/model/Torrent.java
Normal file
@@ -0,0 +1,359 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import static com.torrent4j.util.HashType.MD5;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.TorrentController;
|
||||
import com.torrent4j.model.metadata.MetadataFile;
|
||||
import com.torrent4j.model.metadata.MetadataInfo;
|
||||
import com.torrent4j.model.metadata.TorrentMetadata;
|
||||
import com.torrent4j.net.TorrentProtocol;
|
||||
import com.torrent4j.strategy.TorrentStrategy;
|
||||
import com.torrent4j.strategy.standard.StandardTorrentStrategy;
|
||||
import com.torrent4j.util.Range;
|
||||
import com.torrent4j.util.bencoding.BDecoder;
|
||||
import com.torrent4j.util.bencoding.BMap;
|
||||
|
||||
/**
|
||||
* The main Torrent instance class. With this class, all torrent state is stored
|
||||
* and external interaction can be performed.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class Torrent {
|
||||
/**
|
||||
* The torrent strategy
|
||||
*/
|
||||
private final TorrentStrategy strategy;
|
||||
|
||||
/**
|
||||
* The torrent meta data
|
||||
*/
|
||||
private final TorrentMetadata metadata;
|
||||
/**
|
||||
* The torrent hash
|
||||
*/
|
||||
private final TorrentHash hash;
|
||||
|
||||
/**
|
||||
* The torrent pieces
|
||||
*/
|
||||
private final List<TorrentPiece> pieces = new ArrayList<>();
|
||||
/**
|
||||
* The torrent files
|
||||
*/
|
||||
private final List<TorrentFile> files = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The torrent traffic control
|
||||
*/
|
||||
private final TorrentTrafficControl trafficControl = new TorrentTrafficControl(this);
|
||||
/**
|
||||
* The torrent swarm (list of all peers)
|
||||
*/
|
||||
private final TorrentSwarm swarm = new TorrentSwarm(this);
|
||||
/**
|
||||
* The torrent already downloaded pieces
|
||||
*/
|
||||
private final TorrentCompletePieces completedPieces;
|
||||
|
||||
/**
|
||||
* The current controller for this torrent object. If not attached to any
|
||||
* controller, its value is <code>null</code>.
|
||||
*/
|
||||
private TorrentController controller;
|
||||
|
||||
/**
|
||||
* Creates a new torrent instance
|
||||
*
|
||||
* @param strategy
|
||||
* the torrent strategy
|
||||
* @param metadata
|
||||
* the torrent meta data
|
||||
*/
|
||||
public Torrent(TorrentStrategy strategy, TorrentMetadata metadata) {
|
||||
this.strategy = strategy;
|
||||
this.metadata = metadata;
|
||||
this.hash = new TorrentHash(this, metadata.getInfoHash());
|
||||
|
||||
// parse file list
|
||||
final MetadataInfo info = metadata.getInfo();
|
||||
final byte[] pieceHashes = info.getPieceHashes();
|
||||
final int pieces = pieceHashes.length / 20;
|
||||
long piecesLength = 0;
|
||||
for (int i = 0; i < pieces; i++) {
|
||||
final byte[] hash = Arrays.copyOfRange(pieceHashes, i * 20,
|
||||
(i + 1) * 20);
|
||||
final int length = (int) (i + 1 == pieces ? (info.getLength() - piecesLength)
|
||||
: info.getPieceLength());
|
||||
|
||||
this.pieces.add(new TorrentPiece(this, hash, i, info
|
||||
.getPieceLength() * i, length));
|
||||
piecesLength += length;
|
||||
}
|
||||
|
||||
final Path torrentPath = Paths.get(info.getName());
|
||||
long offset = 0;
|
||||
for (final MetadataFile metaFile : info.getFiles()) {
|
||||
byte[] hash = null;
|
||||
if (metaFile.getHash() != null)
|
||||
hash = MD5.fromString(metaFile.getHash());
|
||||
final List<TorrentPiece> filePieces = getPieces(Range
|
||||
.getRangeByLength(offset, metaFile.getLength()));
|
||||
final TorrentFile file = new TorrentFile(this, offset,
|
||||
metaFile.getLength(), filePieces,
|
||||
torrentPath.resolve(metaFile.getFileName()), hash);
|
||||
files.add(file);
|
||||
for (final TorrentPiece piece : filePieces) {
|
||||
piece.addFile(file);
|
||||
}
|
||||
offset += metaFile.getLength();
|
||||
}
|
||||
|
||||
completedPieces = new TorrentCompletePieces(this);
|
||||
|
||||
// try {
|
||||
// this.localPeer.setPeerID(new String(Hex
|
||||
// .decodeHex("851054102530302d9c640cd409c769266ad3a04f"
|
||||
// .toCharArray())));
|
||||
// } catch (DecoderException e) {
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new torrent instance with {@link StandardTorrentStrategy} as
|
||||
* default strategy
|
||||
*
|
||||
* @param metadata
|
||||
* the torrent meta data
|
||||
*/
|
||||
public Torrent(TorrentMetadata metadata) {
|
||||
this(new StandardTorrentStrategy(), metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this torrent's strategy
|
||||
*/
|
||||
public TorrentStrategy getStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this torrent's meta data
|
||||
*/
|
||||
public TorrentMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this torrent's hash
|
||||
*/
|
||||
public TorrentHash getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this torrent's pieces
|
||||
*/
|
||||
public List<TorrentPiece> getPieces() {
|
||||
return pieces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines all the pieces that contains at least one byte inside the
|
||||
* requested range.
|
||||
*
|
||||
* @param range
|
||||
* the range to look for pieces
|
||||
* @return the pieces inside the requested <code>range</code>
|
||||
*/
|
||||
public List<TorrentPiece> getPieces(Range range) {
|
||||
final List<TorrentPiece> pieces = new ArrayList<>();
|
||||
for (final TorrentPiece piece : this.pieces) {
|
||||
if (piece.getTorrentRange().intersects(range))
|
||||
pieces.add(piece);
|
||||
}
|
||||
return pieces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index
|
||||
* the piece index
|
||||
* @return the piece at the requested index, if any.
|
||||
*/
|
||||
public TorrentPiece getPiece(int index) {
|
||||
if ((index + 1) > pieces.size())
|
||||
return null;
|
||||
return pieces.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all the files for this torrent
|
||||
*/
|
||||
public List<TorrentFile> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines all the files that contains at least one byte inside the
|
||||
* requested range.
|
||||
*
|
||||
* @param range
|
||||
* the range to search for files
|
||||
* @return the list of files inside the requested <code>range</code>
|
||||
*/
|
||||
public List<TorrentFile> getFiles(Range range) {
|
||||
final List<TorrentFile> files = new ArrayList<>();
|
||||
for (final TorrentFile file : this.files) {
|
||||
if (file.getTorrentRange().intersects(range))
|
||||
files.add(file);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sum of all file sizes
|
||||
*/
|
||||
public long getTorrentSize() {
|
||||
long size = 0;
|
||||
for (final TorrentFile file : files) {
|
||||
size += file.getLength();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the trafficControl
|
||||
*/
|
||||
public TorrentTrafficControl getTrafficControl() {
|
||||
return trafficControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the torrent swarm
|
||||
*/
|
||||
public TorrentSwarm getSwarm() {
|
||||
return swarm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the downloaded pieces
|
||||
*/
|
||||
public TorrentCompletePieces getCompletedPieces() {
|
||||
return completedPieces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this torrent's controller, if any.
|
||||
*/
|
||||
public TorrentController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current controller. <b>This method should not be invoked
|
||||
* manually!</b>
|
||||
*
|
||||
* @param controller
|
||||
* the controller to set
|
||||
*/
|
||||
public void setController(TorrentController controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the torrent protocol
|
||||
*/
|
||||
public TorrentProtocol getProtocol() {
|
||||
return controller.getProtocol();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Torrent [hash=" + hash + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an torrent from an {@link InputStream}
|
||||
*
|
||||
* @param in
|
||||
* the {@link InputStream}
|
||||
* @return the loaded {@link Torrent} instance
|
||||
* @throws IOException
|
||||
* if any error occur while reading the torrent file
|
||||
*/
|
||||
public static Torrent load(InputStream in) throws IOException {
|
||||
final Object node = new BDecoder(in).readElement();
|
||||
final TorrentMetadata metadata = new TorrentMetadata((BMap) node);
|
||||
return new Torrent(metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an torrent from an {@link InputStream}
|
||||
*
|
||||
* @param file
|
||||
* the file {@link Path}
|
||||
* @return the loaded {@link Torrent} instance
|
||||
* @throws IOException
|
||||
* if any error occur while reading the torrent file
|
||||
*/
|
||||
public static Torrent load(Path file) throws IOException {
|
||||
return load(Files.newInputStream(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an torrent from an {@link File}
|
||||
*
|
||||
* @param file
|
||||
* the {@link File}
|
||||
* @return the loaded {@link Torrent} instance
|
||||
* @throws IOException
|
||||
* if any error occur while reading the torrent file
|
||||
*/
|
||||
public static Torrent load(File file) throws IOException {
|
||||
if (file == null)
|
||||
throw new InvalidParameterException("File cannot be null");
|
||||
return load(new FileInputStream(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an torrent from an {@link Byte} array
|
||||
*
|
||||
* @param content
|
||||
* the {@link Byte} array
|
||||
* @return the loaded {@link Torrent} instance
|
||||
* @throws IOException
|
||||
* if any error occur while reading the torrent file
|
||||
*/
|
||||
public static Torrent load(byte[] content) throws IOException {
|
||||
return load(new ByteArrayInputStream(content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an torrent from an {@link File}
|
||||
*
|
||||
* @param url
|
||||
* the {@link URL}
|
||||
* @return the loaded {@link Torrent} instance
|
||||
* @throws IOException
|
||||
* if any error occur while reading the torrent file
|
||||
*/
|
||||
public static Torrent load(URL url) throws IOException {
|
||||
if (url == null)
|
||||
throw new InvalidParameterException("File cannot be null");
|
||||
return load(url.openStream());
|
||||
}
|
||||
}
|
||||
37
src/main/java/com/torrent4j/model/TorrentCompletePieces.java
Normal file
37
src/main/java/com/torrent4j/model/TorrentCompletePieces.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
/**
|
||||
* An {@link AbstractTorrentPiecesContainer} dedicated to storing an
|
||||
* {@link Torrent} already downloaded pieces.
|
||||
* <p>
|
||||
* This instance overrides the {@link #addPiece(TorrentPiece)} and
|
||||
* {@link #removePiece(TorrentPiece)} in order to set
|
||||
* {@link TorrentPiece#setDownloaded(boolean)} accordingly, after that, the call
|
||||
* is redirected to the super implementation.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class TorrentCompletePieces extends AbstractTorrentPiecesContainer {
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
*/
|
||||
public TorrentCompletePieces(Torrent torrent) {
|
||||
super(torrent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPiece(TorrentPiece piece) {
|
||||
super.addPiece(piece);
|
||||
piece.setDownloaded(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePiece(TorrentPiece piece) {
|
||||
super.removePiece(piece);
|
||||
piece.setDownloaded(false);
|
||||
}
|
||||
|
||||
}
|
||||
162
src/main/java/com/torrent4j/model/TorrentFile.java
Normal file
162
src/main/java/com/torrent4j/model/TorrentFile.java
Normal file
@@ -0,0 +1,162 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.storage.TorrentStorage;
|
||||
import com.torrent4j.util.Range;
|
||||
|
||||
/**
|
||||
* An file that can be downloaded from a torrent peer.
|
||||
* <p>
|
||||
* Though protocol implementations does not know about files, files themselves
|
||||
* are composed from several pieces (multiple files can be represented by a
|
||||
* single piece and (normally) a file is represented by several pieces -- both
|
||||
* are valid, even simultaneously) that are downloaded and can later be
|
||||
* assembled into a file by an {@link TorrentStorage}.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class TorrentFile {
|
||||
/**
|
||||
* The torrent containing this file
|
||||
*/
|
||||
private final Torrent torrent;
|
||||
/**
|
||||
* The file hash, if present at the metadata file
|
||||
*/
|
||||
private TorrentFileHash hash;
|
||||
|
||||
/**
|
||||
* The file offset (relative to the torrent)
|
||||
*/
|
||||
private final long offset;
|
||||
/**
|
||||
* The file length
|
||||
*/
|
||||
private final long length;
|
||||
|
||||
/**
|
||||
* The list of pieces that contains at least one byte of the file data
|
||||
*/
|
||||
private final List<TorrentPiece> pieces;
|
||||
|
||||
/**
|
||||
* The file path relative to the torrent
|
||||
*/
|
||||
private Path path;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param offset
|
||||
* the file offset (relative to the torrent)
|
||||
* @param length
|
||||
* the file length
|
||||
* @param pieces
|
||||
* the list of pieces that contains at least one byte of the file
|
||||
* data
|
||||
* @param path
|
||||
* the file path relative to the torrent
|
||||
* @param hash
|
||||
* the file hash, if present at the metadata file
|
||||
*/
|
||||
public TorrentFile(Torrent torrent, long offset, long length,
|
||||
List<TorrentPiece> pieces, Path path, byte[] hash) {
|
||||
this.torrent = torrent;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.pieces = pieces;
|
||||
this.path = path;
|
||||
if (hash != null)
|
||||
this.hash = new TorrentFileHash(this, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the file hash, if present at the metadata file
|
||||
*/
|
||||
public TorrentFileHash getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hash
|
||||
* the file hash
|
||||
*/
|
||||
public void setHash(TorrentFileHash hash) {
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the file offset (relative to the torrent)
|
||||
*/
|
||||
public long getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the file length
|
||||
*/
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the range of this file inside the torrent
|
||||
*/
|
||||
public Range getTorrentRange() {
|
||||
return Range.getRangeByLength(offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the range of this file as a single file
|
||||
*/
|
||||
public Range getFileRange() {
|
||||
return Range.getRangeByLength(0, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of pieces that contains at least one byte of the file
|
||||
* data
|
||||
*/
|
||||
public List<TorrentPiece> getPieces() {
|
||||
return pieces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the file path relative to the torrent
|
||||
*/
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path
|
||||
* the file path relative to the torrent
|
||||
*/
|
||||
public void setPath(Path path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the file name
|
||||
*/
|
||||
public String getFileName() {
|
||||
return path.getFileName().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the parent torrent
|
||||
*/
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TorrentFile [torrent=" + torrent + ", hash=" + hash
|
||||
+ ", length=" + length + ", path=" + path + "]";
|
||||
}
|
||||
}
|
||||
21
src/main/java/com/torrent4j/model/TorrentFileHash.java
Normal file
21
src/main/java/com/torrent4j/model/TorrentFileHash.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import com.torrent4j.util.Hash;
|
||||
import com.torrent4j.util.HashType;
|
||||
|
||||
public class TorrentFileHash extends Hash {
|
||||
private final TorrentFile file;
|
||||
|
||||
public TorrentFileHash(TorrentFile file, byte[] hash) {
|
||||
super(HashType.MD5, hash);
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public TorrentFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return file.getTorrent();
|
||||
}
|
||||
}
|
||||
18
src/main/java/com/torrent4j/model/TorrentHash.java
Normal file
18
src/main/java/com/torrent4j/model/TorrentHash.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import com.torrent4j.util.Hash;
|
||||
import com.torrent4j.util.HashType;
|
||||
|
||||
public class TorrentHash extends Hash {
|
||||
private final Torrent torrent;
|
||||
|
||||
public TorrentHash(Torrent torrent, byte[] hash) {
|
||||
super(HashType.SHA1, hash);
|
||||
this.torrent = torrent;
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
|
||||
}
|
||||
233
src/main/java/com/torrent4j/model/TorrentPeer.java
Normal file
233
src/main/java/com/torrent4j/model/TorrentPeer.java
Normal file
@@ -0,0 +1,233 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.torrent4j.model.TorrentPeerState.TorrentPeerChoking;
|
||||
import com.torrent4j.model.TorrentPeerState.TorrentPeerInterest;
|
||||
import com.torrent4j.net.TorrentProtocolPeer;
|
||||
|
||||
/**
|
||||
* Represents a peer on the BitTorrent network. This object contains information
|
||||
* necessary to locate and connect to a peer.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class TorrentPeer {
|
||||
/**
|
||||
* The parent torrent for this peer
|
||||
*/
|
||||
private final Torrent torrent;
|
||||
/**
|
||||
* The pieces this peer has complete
|
||||
*/
|
||||
private final TorrentPeerPieces pieces;
|
||||
|
||||
/**
|
||||
* The peer ID
|
||||
*/
|
||||
private String peerID;
|
||||
|
||||
/**
|
||||
* The protocol peer used to communicate with it
|
||||
*/
|
||||
private TorrentProtocolPeer protocolPeer;
|
||||
/**
|
||||
* The socket address
|
||||
*/
|
||||
private InetSocketAddress address;
|
||||
/**
|
||||
* Whether the peer can be contacted directly (has public accessible IP)
|
||||
*/
|
||||
private boolean accessible;
|
||||
|
||||
/**
|
||||
* This peer's traffic control
|
||||
*/
|
||||
private final TorrentPeerTrafficControl trafficControl = new TorrentPeerTrafficControl(
|
||||
this);
|
||||
/**
|
||||
* The peer state
|
||||
*/
|
||||
private TorrentPeerState state = new TorrentPeerState(this);
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
*/
|
||||
public TorrentPeer(Torrent torrent) {
|
||||
this.torrent = torrent;
|
||||
this.pieces = new TorrentPeerPieces(this);
|
||||
}
|
||||
|
||||
public TorrentPeerPieces getPieces() {
|
||||
return pieces;
|
||||
}
|
||||
|
||||
public String getPeerID() {
|
||||
return peerID;
|
||||
}
|
||||
|
||||
public void setPeerID(String peerID) {
|
||||
this.peerID = peerID;
|
||||
}
|
||||
|
||||
public TorrentProtocolPeer getProtocolPeer() {
|
||||
return protocolPeer;
|
||||
}
|
||||
|
||||
public void setProtocolPeer(TorrentProtocolPeer protocolPeer) {
|
||||
this.protocolPeer = protocolPeer;
|
||||
}
|
||||
|
||||
public boolean connect() {
|
||||
if (protocolPeer == null) {
|
||||
if (torrent.getProtocol() == null)
|
||||
return false;
|
||||
return torrent.getProtocol().connect(this);
|
||||
} else {
|
||||
return protocolPeer.connect();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnectable() {
|
||||
return address != null;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
if (protocolPeer == null)
|
||||
return false;
|
||||
return protocolPeer.isConnected();
|
||||
}
|
||||
|
||||
public boolean disconnect() {
|
||||
if (protocolPeer == null)
|
||||
return false;
|
||||
return protocolPeer.disconnect();
|
||||
}
|
||||
|
||||
public InetSocketAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(InetSocketAddress address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public final InetAddress getIP() {
|
||||
return address.getAddress();
|
||||
}
|
||||
|
||||
public final int getPort() {
|
||||
return address.getPort();
|
||||
}
|
||||
|
||||
public final String getHostName(boolean resolve) {
|
||||
return resolve ? address.getHostName() : address.getHostString();
|
||||
}
|
||||
|
||||
public final String getHostName() {
|
||||
return getHostName(false);
|
||||
}
|
||||
|
||||
public boolean isAccessible() {
|
||||
return accessible;
|
||||
}
|
||||
|
||||
public void setAccessible(boolean accessible) {
|
||||
this.accessible = accessible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the traffic shaper
|
||||
*/
|
||||
public TorrentPeerTrafficControl getTrafficControl() {
|
||||
return trafficControl;
|
||||
}
|
||||
|
||||
public TorrentPeerState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void resetState() {
|
||||
state = new TorrentPeerState(this);
|
||||
}
|
||||
|
||||
// NETWORK RELATED THINGS!
|
||||
public void handshake() {
|
||||
// FIXME sent another peer id
|
||||
protocolPeer.handshake(torrent.getHash().getHash(), torrent
|
||||
.getController().getConfig().getPeerID());
|
||||
}
|
||||
|
||||
public void declareInterest() {
|
||||
if (state.isLocalllyInterested())
|
||||
return;
|
||||
protocolPeer.interested();
|
||||
state.setLocalInterest(TorrentPeerInterest.INTERESTED);
|
||||
}
|
||||
|
||||
public void withdrawInterest() {
|
||||
if (!state.isLocalllyInterested())
|
||||
return;
|
||||
protocolPeer.notInterested();
|
||||
state.setLocalInterest(TorrentPeerInterest.NOT_INTERESTED);
|
||||
}
|
||||
|
||||
public void choke() {
|
||||
if (state.isLocallyChoked())
|
||||
return;
|
||||
protocolPeer.choke();
|
||||
state.setLocallyChoked(TorrentPeerChoking.CHOKED);
|
||||
}
|
||||
|
||||
public void unchoke() {
|
||||
if (!state.isLocallyChoked())
|
||||
return;
|
||||
protocolPeer.unchoke();
|
||||
state.setLocallyChoked(TorrentPeerChoking.UNCHOKED);
|
||||
}
|
||||
|
||||
public void have(TorrentPiece piece) {
|
||||
protocolPeer.have(piece.getIndex());
|
||||
}
|
||||
|
||||
public void bitField() {
|
||||
protocolPeer.bitField(torrent.getCompletedPieces().getBitSet());
|
||||
}
|
||||
|
||||
public void requestBlock(TorrentPieceBlock block) {
|
||||
protocolPeer.requestBlock(block.getPiece().getIndex(),
|
||||
block.getOffset(), block.getLength());
|
||||
}
|
||||
|
||||
public void cancelRequestedBlock(TorrentPieceBlock block) {
|
||||
protocolPeer.cancelRequestedBlock(block.getPiece().getIndex(),
|
||||
block.getOffset(), block.getLength());
|
||||
}
|
||||
|
||||
public void sendBlock(TorrentPieceBlock block, ByteBuffer data) {
|
||||
protocolPeer.sendBlock(block.getPiece().getIndex(), block.getOffset(),
|
||||
data);
|
||||
}
|
||||
|
||||
public void sendPort(int port) {
|
||||
protocolPeer.port(port);
|
||||
}
|
||||
|
||||
public void keepAlive() {
|
||||
protocolPeer.keepAlive();
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TorrentPeer [torrent=" + torrent + ", address=" + address + "]";
|
||||
}
|
||||
}
|
||||
24
src/main/java/com/torrent4j/model/TorrentPeerPieces.java
Normal file
24
src/main/java/com/torrent4j/model/TorrentPeerPieces.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
|
||||
public class TorrentPeerPieces extends AbstractTorrentPiecesContainer {
|
||||
private final TorrentPeer peer;
|
||||
|
||||
public void load(BitSet bitSet) {
|
||||
this.bitSet.set(0, this.bitSet.size(), false);
|
||||
for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
|
||||
this.bitSet.set(i, true);
|
||||
}
|
||||
}
|
||||
|
||||
public TorrentPeerPieces(TorrentPeer peer) {
|
||||
super(peer.getTorrent());
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
public TorrentPeer getPeer() {
|
||||
return peer;
|
||||
}
|
||||
}
|
||||
266
src/main/java/com/torrent4j/model/TorrentPeerState.java
Normal file
266
src/main/java/com/torrent4j/model/TorrentPeerState.java
Normal file
@@ -0,0 +1,266 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class TorrentPeerState {
|
||||
private final TorrentPeer peer;
|
||||
|
||||
private TorrentPeerInterest remoteInterest = TorrentPeerInterest.NOT_INTERESTED;
|
||||
private TorrentPeerInterest localInterest = TorrentPeerInterest.NOT_INTERESTED;
|
||||
|
||||
public enum TorrentPeerInterest {
|
||||
INTERESTED, NOT_INTERESTED;
|
||||
}
|
||||
|
||||
private TorrentPeerChoking remoteChoked = TorrentPeerChoking.CHOKED;
|
||||
private TorrentPeerChoking locallyChoked = TorrentPeerChoking.CHOKED;
|
||||
|
||||
public enum TorrentPeerChoking {
|
||||
CHOKED, UNCHOKED;
|
||||
}
|
||||
|
||||
private TorrentPieceBlock downloadRequestedBlock;
|
||||
private Date downloadRequestedDate;
|
||||
private TorrentPieceBlock lastDownloadedBlock;
|
||||
private Date lastDownloadedBlockDate;
|
||||
|
||||
private TorrentPieceBlock uploadRequestedBlock;
|
||||
private Date uploadRequestedDate;
|
||||
private TorrentPieceBlock lastUploadedBlock;
|
||||
private Date lastUploadedBlockDate;
|
||||
|
||||
public TorrentPeerState(TorrentPeer peer) {
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the remoteInterest
|
||||
*/
|
||||
public TorrentPeerInterest getRemoteInterest() {
|
||||
return remoteInterest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the remoteInterest
|
||||
*/
|
||||
public boolean isRemotellyInterested() {
|
||||
return remoteInterest == TorrentPeerInterest.INTERESTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param remoteInterest
|
||||
* the remoteInterest to set
|
||||
*/
|
||||
public void setRemoteInterest(TorrentPeerInterest remoteInterest) {
|
||||
this.remoteInterest = remoteInterest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the localInterest
|
||||
*/
|
||||
public TorrentPeerInterest getLocalInterest() {
|
||||
return localInterest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the localInterest
|
||||
*/
|
||||
public boolean isLocalllyInterested() {
|
||||
return localInterest == TorrentPeerInterest.INTERESTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param localInterest
|
||||
* the localInterest to set
|
||||
*/
|
||||
public void setLocalInterest(TorrentPeerInterest localInterest) {
|
||||
this.localInterest = localInterest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the remoteChoked
|
||||
*/
|
||||
public TorrentPeerChoking getRemoteChoked() {
|
||||
return remoteChoked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the remoteChoked
|
||||
*/
|
||||
public boolean isRemotellyChoked() {
|
||||
return remoteChoked == TorrentPeerChoking.CHOKED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param remoteChoked
|
||||
* the remoteChoked to set
|
||||
*/
|
||||
public void setRemoteChoked(TorrentPeerChoking remoteChoked) {
|
||||
this.remoteChoked = remoteChoked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the locallyChoked
|
||||
*/
|
||||
public TorrentPeerChoking getLocallyChoked() {
|
||||
return locallyChoked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the locallyChoked
|
||||
*/
|
||||
public boolean isLocallyChoked() {
|
||||
return locallyChoked == TorrentPeerChoking.CHOKED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param locallyChoked
|
||||
* the locallyChoked to set
|
||||
*/
|
||||
public void setLocallyChoked(TorrentPeerChoking locallyChoked) {
|
||||
this.locallyChoked = locallyChoked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the downloadRequestedBlock
|
||||
*/
|
||||
public TorrentPieceBlock getDownloadRequestedBlock() {
|
||||
return downloadRequestedBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the downloadRequestedBlock
|
||||
*/
|
||||
public boolean hasDownloadRequestedBlock() {
|
||||
return downloadRequestedBlock != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param downloadRequestedBlock
|
||||
* the downloadRequestedBlock to set
|
||||
*/
|
||||
public void setDownloadRequestedBlock(
|
||||
TorrentPieceBlock downloadRequestedBlock) {
|
||||
this.downloadRequestedBlock = downloadRequestedBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the downloadRequestedDate
|
||||
*/
|
||||
public Date getDownloadRequestedDate() {
|
||||
return downloadRequestedDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param downloadRequestedDate
|
||||
* the downloadRequestedDate to set
|
||||
*/
|
||||
public void setDownloadRequestedDate(Date downloadRequestedDate) {
|
||||
this.downloadRequestedDate = downloadRequestedDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastDownloadedBlock
|
||||
*/
|
||||
public TorrentPieceBlock getLastDownloadedBlock() {
|
||||
return lastDownloadedBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lastDownloadedBlock
|
||||
* the lastDownloadedBlock to set
|
||||
*/
|
||||
public void setLastDownloadedBlock(TorrentPieceBlock lastDownloadedBlock) {
|
||||
this.lastDownloadedBlock = lastDownloadedBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastDownloadedBlockDate
|
||||
*/
|
||||
public Date getLastDownloadedBlockDate() {
|
||||
return lastDownloadedBlockDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lastDownloadedBlockDate
|
||||
* the lastDownloadedBlockDate to set
|
||||
*/
|
||||
public void setLastDownloadedBlockDate(Date lastDownloadedBlockDate) {
|
||||
this.lastDownloadedBlockDate = lastDownloadedBlockDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the uploadRequestedBlock
|
||||
*/
|
||||
public TorrentPieceBlock getUploadRequestedBlock() {
|
||||
return uploadRequestedBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the uploadRequestedBlock
|
||||
*/
|
||||
public boolean hasUploadRequestedBlock() {
|
||||
return uploadRequestedBlock != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uploadRequestedBlock
|
||||
* the uploadRequestedBlock to set
|
||||
*/
|
||||
public void setUploadRequestedBlock(TorrentPieceBlock uploadRequestedBlock) {
|
||||
this.uploadRequestedBlock = uploadRequestedBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the uploadRequestedDate
|
||||
*/
|
||||
public Date getUploadRequestedDate() {
|
||||
return uploadRequestedDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uploadRequestedDate
|
||||
* the uploadRequestedDate to set
|
||||
*/
|
||||
public void setUploadRequestedDate(Date uploadRequestedDate) {
|
||||
this.uploadRequestedDate = uploadRequestedDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastUploadedBlock
|
||||
*/
|
||||
public TorrentPieceBlock getLastUploadedBlock() {
|
||||
return lastUploadedBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lastUploadedBlock
|
||||
* the lastUploadedBlock to set
|
||||
*/
|
||||
public void setLastUploadedBlock(TorrentPieceBlock lastUploadedBlock) {
|
||||
this.lastUploadedBlock = lastUploadedBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastUploadedBlockDate
|
||||
*/
|
||||
public Date getLastUploadedBlockDate() {
|
||||
return lastUploadedBlockDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lastUploadedBlockDate
|
||||
* the lastUploadedBlockDate to set
|
||||
*/
|
||||
public void setLastUploadedBlockDate(Date lastUploadedBlockDate) {
|
||||
this.lastUploadedBlockDate = lastUploadedBlockDate;
|
||||
}
|
||||
|
||||
public TorrentPeer getPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return peer.getTorrent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
public class TorrentPeerTrafficControl extends TorrentTrafficControl {
|
||||
private final TorrentPeer peer;
|
||||
|
||||
public TorrentPeerTrafficControl(TorrentPeer peer) {
|
||||
super(peer.getTorrent());
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the peer
|
||||
*/
|
||||
public TorrentPeer getPeer() {
|
||||
return peer;
|
||||
}
|
||||
}
|
||||
128
src/main/java/com/torrent4j/model/TorrentPiece.java
Normal file
128
src/main/java/com/torrent4j/model/TorrentPiece.java
Normal file
@@ -0,0 +1,128 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.util.Range;
|
||||
|
||||
public class TorrentPiece {
|
||||
private final Torrent torrent;
|
||||
private final TorrentPieceHash hash;
|
||||
|
||||
private final int index;
|
||||
private final int offset;
|
||||
private final int length;
|
||||
|
||||
private final List<TorrentPieceBlock> blocks = new ArrayList<>();
|
||||
private final List<TorrentFile> files = new ArrayList<>();
|
||||
|
||||
public TorrentPiece(Torrent torrent, byte[] hash, int index, int offset,
|
||||
int length) {
|
||||
this.torrent = torrent;
|
||||
this.hash = new TorrentPieceHash(this, hash);
|
||||
this.index = index;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
|
||||
final int blocks = (int) Math.ceil((double) length
|
||||
/ TorrentPieceBlock.BLOCK_LENGTH);
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
final int blockOffset = TorrentPieceBlock.BLOCK_LENGTH * i;
|
||||
int len = TorrentPieceBlock.BLOCK_LENGTH;
|
||||
if (i == blocks - 1 && length % TorrentPieceBlock.BLOCK_LENGTH != 0)
|
||||
len = length - blockOffset;
|
||||
this.blocks.add(new TorrentPieceBlock(this, blockOffset, len));
|
||||
}
|
||||
}
|
||||
|
||||
public TorrentPieceHash getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public long getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public Range getTorrentRange() {
|
||||
return Range.getRangeByLength(offset, length);
|
||||
}
|
||||
|
||||
public boolean isFirst() {
|
||||
return index == 0;
|
||||
}
|
||||
|
||||
public boolean isLast() {
|
||||
return torrent.getPieces().size() == index + 1;
|
||||
}
|
||||
|
||||
public List<TorrentPieceBlock> getBlocks() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public TorrentPieceBlock getBlock(int offset, int length) {
|
||||
for (final TorrentPieceBlock block : blocks) {
|
||||
if (block.getOffset() == offset && block.getLength() == length)
|
||||
return block;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TorrentPieceBlock getFirstBlock() {
|
||||
return blocks.get(0);
|
||||
}
|
||||
|
||||
public TorrentPieceBlock getLastBlock() {
|
||||
return blocks.get(blocks.size() - 1);
|
||||
}
|
||||
|
||||
public TorrentPieceBlock getNextBlock(TorrentPieceBlock block) {
|
||||
int next = blocks.indexOf(block) + 1;
|
||||
if (blocks.size() == next)
|
||||
return null;
|
||||
return blocks.get(next);
|
||||
}
|
||||
|
||||
public boolean isDownloaded() {
|
||||
for (final TorrentPieceBlock block : blocks) {
|
||||
if (!block.isDownloaded())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setDownloaded(boolean downloaded) {
|
||||
for (final TorrentPieceBlock block : blocks) {
|
||||
block.setDownloaded(downloaded);
|
||||
}
|
||||
}
|
||||
|
||||
public List<TorrentFile> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
/* package protected! */void addFile(TorrentFile file) {
|
||||
files.add(file);
|
||||
}
|
||||
|
||||
public TorrentPiece getNextPiece() {
|
||||
return torrent.getPiece(index + 1);
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TorrentPiece [torrent=" + torrent + ", hash=" + hash
|
||||
+ ", index=" + index + ", length=" + length + "]";
|
||||
}
|
||||
}
|
||||
81
src/main/java/com/torrent4j/model/TorrentPieceBlock.java
Normal file
81
src/main/java/com/torrent4j/model/TorrentPieceBlock.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import com.torrent4j.util.Range;
|
||||
|
||||
public class TorrentPieceBlock {
|
||||
public static final int BLOCK_LENGTH = 16 * 1024;
|
||||
|
||||
private final TorrentPiece piece;
|
||||
private final int offset;
|
||||
private final int length;
|
||||
|
||||
private boolean downloaded = false;
|
||||
|
||||
public TorrentPieceBlock(TorrentPiece piece, int offset, int length) {
|
||||
this.piece = piece;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public Range getTorrentRange() {
|
||||
return Range.getRangeByLength(piece.getOffset() + offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range inside the piece.
|
||||
*
|
||||
* @return the range in piece
|
||||
*/
|
||||
public Range getPieceRange() {
|
||||
return Range.getRangeByLength(offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range inside the file
|
||||
*
|
||||
* @param file
|
||||
* the file
|
||||
* @return the range in file
|
||||
*/
|
||||
public Range getFileRange(TorrentFile file) {
|
||||
if (piece.getOffset() - file.getOffset() + offset < 0)
|
||||
return null;
|
||||
return Range.getRangeByLength(
|
||||
piece.getOffset() - file.getOffset() + offset, length)
|
||||
.intersection(file.getFileRange());
|
||||
}
|
||||
|
||||
public boolean isDownloaded() {
|
||||
return downloaded;
|
||||
}
|
||||
|
||||
public void setDownloaded(boolean downloaded) {
|
||||
this.downloaded = downloaded;
|
||||
}
|
||||
|
||||
public TorrentPieceBlock getNextBlock() {
|
||||
return piece.getNextBlock(this);
|
||||
}
|
||||
|
||||
public TorrentPiece getPiece() {
|
||||
return piece;
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return piece.getTorrent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TorrentPieceBlock [piece=" + piece + ", offset=" + offset
|
||||
+ ", length=" + length + "]";
|
||||
}
|
||||
}
|
||||
21
src/main/java/com/torrent4j/model/TorrentPieceHash.java
Normal file
21
src/main/java/com/torrent4j/model/TorrentPieceHash.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import com.torrent4j.util.Hash;
|
||||
import com.torrent4j.util.HashType;
|
||||
|
||||
public class TorrentPieceHash extends Hash {
|
||||
private final TorrentPiece piece;
|
||||
|
||||
public TorrentPieceHash(TorrentPiece piece, byte[] hash) {
|
||||
super(HashType.SHA1, hash);
|
||||
this.piece = piece;
|
||||
}
|
||||
|
||||
public TorrentPiece getPiece() {
|
||||
return piece;
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return piece.getTorrent();
|
||||
}
|
||||
}
|
||||
69
src/main/java/com/torrent4j/model/TorrentSwarm.java
Normal file
69
src/main/java/com/torrent4j/model/TorrentSwarm.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class TorrentSwarm {
|
||||
private final Torrent torrent;
|
||||
private final List<TorrentPeer> peers = new ArrayList<>();
|
||||
|
||||
public TorrentSwarm(Torrent torrent) {
|
||||
this.torrent = torrent;
|
||||
}
|
||||
|
||||
public void broadcast(SwarmBroadcastHandler handler) {
|
||||
for (final TorrentPeer peer : getConnectedPeers()) {
|
||||
try {
|
||||
handler.broadcast(peer);
|
||||
} catch (Exception e) {
|
||||
if (handler.exception(e))
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface SwarmBroadcastHandler {
|
||||
void broadcast(TorrentPeer peer);
|
||||
|
||||
boolean exception(Exception e);
|
||||
}
|
||||
|
||||
public List<TorrentPeer> getPeers() {
|
||||
return Collections.unmodifiableList(peers);
|
||||
}
|
||||
|
||||
public List<TorrentPeer> getConnectedPeers() {
|
||||
final List<TorrentPeer> list = new ArrayList<>();
|
||||
for (final TorrentPeer peer : peers) {
|
||||
if (peer.isConnected())
|
||||
list.add(peer);
|
||||
}
|
||||
return Collections.unmodifiableList(list);
|
||||
}
|
||||
|
||||
public void addPeer(TorrentPeer peer) {
|
||||
peers.add(peer);
|
||||
torrent.getStrategy().getPeerStrategy().peerDiscovered(torrent, peer);
|
||||
}
|
||||
|
||||
public void removePeer(TorrentPeer peer) {
|
||||
peers.remove(peer);
|
||||
torrent.getStrategy().getPeerStrategy().peerRemoved(torrent, peer);
|
||||
}
|
||||
|
||||
public TorrentPeer findPeer(InetSocketAddress address, String peerID) {
|
||||
for (final TorrentPeer peer : peers) {
|
||||
if (peerID != null && peerID.equals(peer.getPeerID()))
|
||||
return peer;
|
||||
if (address != null && address.equals(peer.getAddress()))
|
||||
return peer;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
}
|
||||
13
src/main/java/com/torrent4j/model/TorrentTracker.java
Normal file
13
src/main/java/com/torrent4j/model/TorrentTracker.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
public class TorrentTracker {
|
||||
private final Torrent torrent;
|
||||
|
||||
public TorrentTracker(Torrent torrent) {
|
||||
this.torrent = torrent;
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
}
|
||||
91
src/main/java/com/torrent4j/model/TorrentTrafficControl.java
Normal file
91
src/main/java/com/torrent4j/model/TorrentTrafficControl.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package com.torrent4j.model;
|
||||
|
||||
public class TorrentTrafficControl {
|
||||
private final Torrent torrent;
|
||||
|
||||
private long downloadSpeedLimit = 0;
|
||||
private long uploadSpeedLimit = 0;
|
||||
|
||||
private long currentDownloadSpeed = 0;
|
||||
private long currentUploadSpeed = 0;
|
||||
|
||||
public TorrentTrafficControl(Torrent torrent) {
|
||||
this.torrent = torrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the downloadSpeedLimit
|
||||
*/
|
||||
public long getDownloadSpeedLimit() {
|
||||
return downloadSpeedLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param downloadSpeedLimit
|
||||
* the downloadSpeedLimit to set
|
||||
*/
|
||||
public void setDownloadSpeedLimit(long downloadSpeedLimit) {
|
||||
this.downloadSpeedLimit = downloadSpeedLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the uploadSpeedLimit
|
||||
*/
|
||||
public long getUploadSpeedLimit() {
|
||||
return uploadSpeedLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uploadSpeedLimit
|
||||
* the uploadSpeedLimit to set
|
||||
*/
|
||||
public void setUploadSpeedLimit(long uploadSpeedLimit) {
|
||||
this.uploadSpeedLimit = uploadSpeedLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the currentDownloadSpeed
|
||||
*/
|
||||
public long getCurrentDownloadSpeed() {
|
||||
return currentDownloadSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currentDownloadSpeed
|
||||
* the currentDownloadSpeed to set
|
||||
*/
|
||||
public void setCurrentDownloadSpeed(long currentDownloadSpeed) {
|
||||
this.currentDownloadSpeed = currentDownloadSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the currentUploadSpeed
|
||||
*/
|
||||
public long getCurrentUploadSpeed() {
|
||||
return currentUploadSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currentUploadSpeed
|
||||
* the currentUploadSpeed to set
|
||||
*/
|
||||
public void setCurrentUploadSpeed(long currentUploadSpeed) {
|
||||
this.currentUploadSpeed = currentUploadSpeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TorrentTrafficControl [torrent=" + torrent
|
||||
+ ", downloadSpeedLimit=" + downloadSpeedLimit
|
||||
+ ", uploadSpeedLimit=" + uploadSpeedLimit
|
||||
+ ", currentDownloadSpeed=" + currentDownloadSpeed
|
||||
+ ", currentUploadSpeed=" + currentUploadSpeed + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the torrent
|
||||
*/
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
}
|
||||
51
src/main/java/com/torrent4j/model/metadata/MetadataFile.java
Normal file
51
src/main/java/com/torrent4j/model/metadata/MetadataFile.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.torrent4j.model.metadata;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.torrent4j.util.bencoding.BList;
|
||||
import com.torrent4j.util.bencoding.BMap;
|
||||
|
||||
public class MetadataFile {
|
||||
private String fileName;
|
||||
private long length;
|
||||
private String hash;
|
||||
|
||||
public MetadataFile(BMap file) throws IOException {
|
||||
if (file.get("path") != null) {
|
||||
final BList path = file.getList("path");
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final Object pathPart : path) {
|
||||
builder.append(new String((byte[]) pathPart)).append("/");
|
||||
}
|
||||
this.fileName = builder.substring(0, builder.length() - 1);
|
||||
} else {
|
||||
this.fileName = file.getString("name");
|
||||
}
|
||||
this.length = file.getLong("length");
|
||||
this.hash = file.getString("md5sum");
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(long length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public void setHash(String hash) {
|
||||
this.hash = hash;
|
||||
}
|
||||
}
|
||||
83
src/main/java/com/torrent4j/model/metadata/MetadataInfo.java
Normal file
83
src/main/java/com/torrent4j/model/metadata/MetadataInfo.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package com.torrent4j.model.metadata;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.util.bencoding.BList;
|
||||
import com.torrent4j.util.bencoding.BMap;
|
||||
|
||||
public class MetadataInfo {
|
||||
private int pieceLength;
|
||||
private byte[] pieceHashes;
|
||||
private boolean privateTorrent;
|
||||
|
||||
private String name;
|
||||
private List<MetadataFile> files = new ArrayList<>();
|
||||
|
||||
public MetadataInfo(BMap dictionary) throws IOException {
|
||||
this.pieceLength = dictionary.getInteger("piece length");
|
||||
this.pieceHashes = (byte[]) dictionary.get("pieces");
|
||||
if (dictionary.get("private") != null)
|
||||
this.privateTorrent = dictionary.getInteger("private") == 1;
|
||||
this.name = dictionary.getString("name");
|
||||
|
||||
if (dictionary.get("files") != null) {
|
||||
final BList files = dictionary
|
||||
.getList("files");
|
||||
for (final Object file : files) {
|
||||
this.files.add(new MetadataFile((BMap) file));
|
||||
}
|
||||
} else {
|
||||
this.files.add(new MetadataFile(dictionary));
|
||||
}
|
||||
}
|
||||
|
||||
public int getPieceLength() {
|
||||
return pieceLength;
|
||||
}
|
||||
|
||||
public void setPieceLength(int pieceLength) {
|
||||
this.pieceLength = pieceLength;
|
||||
}
|
||||
|
||||
public byte[] getPieceHashes() {
|
||||
return pieceHashes;
|
||||
}
|
||||
|
||||
public void setPieceHashes(byte[] pieceHashes) {
|
||||
this.pieceHashes = pieceHashes;
|
||||
}
|
||||
|
||||
public boolean isPrivateTorrent() {
|
||||
return privateTorrent;
|
||||
}
|
||||
|
||||
public void setPrivateTorrent(boolean privateTorrent) {
|
||||
this.privateTorrent = privateTorrent;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<MetadataFile> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public void setFiles(List<MetadataFile> files) {
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
long length = 0;
|
||||
for (final MetadataFile file : files) {
|
||||
length += file.getLength();
|
||||
}
|
||||
return length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.torrent4j.model.metadata;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.util.bencoding.BList;
|
||||
|
||||
public class MetadataTracker {
|
||||
private String url;
|
||||
private List<String> backupUrls;
|
||||
|
||||
public MetadataTracker(String url, List<String> backupUrls) {
|
||||
this.url = url;
|
||||
this.backupUrls = backupUrls;
|
||||
}
|
||||
|
||||
public MetadataTracker(String url, String... backupUrls) {
|
||||
this(url, Arrays.asList(backupUrls));
|
||||
}
|
||||
|
||||
public MetadataTracker(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public MetadataTracker(BList urls) throws IOException {
|
||||
this.url = urls.getString(0);
|
||||
if(urls.size() > 1) {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
public String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setURL(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public List<String> getBackupURLs() {
|
||||
return backupUrls;
|
||||
}
|
||||
|
||||
public void setBackupURLs(List<String> backupUrls) {
|
||||
this.backupUrls = backupUrls;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.torrent4j.model.metadata;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.util.HashType;
|
||||
import com.torrent4j.util.bencoding.BEncoder;
|
||||
import com.torrent4j.util.bencoding.BList;
|
||||
import com.torrent4j.util.bencoding.BMap;
|
||||
|
||||
public class TorrentMetadata {
|
||||
private MetadataInfo info;
|
||||
private byte[] infoHash;
|
||||
|
||||
private List<MetadataTracker> trackers = new ArrayList<>();
|
||||
private Date creationDate;
|
||||
|
||||
public TorrentMetadata(BMap dictionary) throws IOException {
|
||||
this.trackers
|
||||
.add(new MetadataTracker(dictionary.getString("announce")));
|
||||
final BList announceListNode = dictionary.getList("announce-list");
|
||||
if (announceListNode != null) {
|
||||
for (final Object trackerGroup : announceListNode) {
|
||||
trackers.add(new MetadataTracker((BList) trackerGroup));
|
||||
}
|
||||
}
|
||||
if (dictionary.get("creation date") != null)
|
||||
this.creationDate = new Date(
|
||||
dictionary.getInteger("creation date") * 1000);
|
||||
|
||||
this.infoHash = HashType.SHA1.hash(BEncoder.bencode(dictionary
|
||||
.get("info")));
|
||||
this.info = new MetadataInfo(dictionary.getMap("info"));
|
||||
}
|
||||
|
||||
public byte[] getInfoHash() {
|
||||
return infoHash;
|
||||
}
|
||||
|
||||
public void setInfoHash(byte[] infoHash) {
|
||||
this.infoHash = infoHash;
|
||||
}
|
||||
|
||||
public MetadataInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public void setInfo(MetadataInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public List<MetadataTracker> getTrackers() {
|
||||
return trackers;
|
||||
}
|
||||
|
||||
public MetadataTracker getMainTracker() {
|
||||
return trackers.get(0);
|
||||
}
|
||||
|
||||
public void setTrackers(List<MetadataTracker> trackers) {
|
||||
this.trackers = trackers;
|
||||
}
|
||||
|
||||
public Date getCreationDate() {
|
||||
return creationDate;
|
||||
}
|
||||
|
||||
public void setCreationDate(Date creationDate) {
|
||||
this.creationDate = creationDate;
|
||||
}
|
||||
}
|
||||
12
src/main/java/com/torrent4j/net/TorrentProtocol.java
Normal file
12
src/main/java/com/torrent4j/net/TorrentProtocol.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.torrent4j.net;
|
||||
|
||||
import com.torrent4j.TorrentController;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
|
||||
public interface TorrentProtocol {
|
||||
void start(TorrentController controller, int listenPort);
|
||||
|
||||
void stop();
|
||||
|
||||
boolean connect(TorrentPeer peer);
|
||||
}
|
||||
43
src/main/java/com/torrent4j/net/TorrentProtocolPeer.java
Normal file
43
src/main/java/com/torrent4j/net/TorrentProtocolPeer.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.torrent4j.net;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.BitSet;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
|
||||
public interface TorrentProtocolPeer {
|
||||
public TorrentPeer getTorrentPeer();
|
||||
|
||||
public Torrent getTorrent();
|
||||
|
||||
boolean connect();
|
||||
|
||||
boolean isConnected();
|
||||
|
||||
boolean disconnect();
|
||||
|
||||
void handshake(byte[] torrentHash, String peerID);
|
||||
|
||||
void requestBlock(int pieceIndex, int start, int length);
|
||||
|
||||
void cancelRequestedBlock(int pieceIndex, int start, int length);
|
||||
|
||||
void sendBlock(int pieceIndex, int start, ByteBuffer data);
|
||||
|
||||
void bitField(BitSet bitSet);
|
||||
|
||||
void have(int pieceIndex);
|
||||
|
||||
void choke();
|
||||
|
||||
void unchoke();
|
||||
|
||||
void interested();
|
||||
|
||||
void notInterested();
|
||||
|
||||
void port(int dhtPort);
|
||||
|
||||
void keepAlive();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.torrent4j.net;
|
||||
|
||||
public interface TorrentTrafficShaper {
|
||||
void update(long writeLimit, long readLimit);
|
||||
|
||||
long getDownloadSpeed();
|
||||
long getUploadSpeed();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.torrent4j.net.peerwire;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
public abstract class AbstractPeerWireMessage implements PeerWireMessage {
|
||||
public final int messageID;
|
||||
|
||||
public AbstractPeerWireMessage(int messageID) {
|
||||
this.messageID = messageID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(ChannelBuffer buffer) {
|
||||
buffer.writeByte(messageID);
|
||||
writeImpl(buffer);
|
||||
}
|
||||
|
||||
public void writeImpl(ChannelBuffer buffer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void read(ChannelBuffer buffer) {
|
||||
readImpl(buffer);
|
||||
}
|
||||
|
||||
public void readImpl(ChannelBuffer buffer) {
|
||||
}
|
||||
}
|
||||
270
src/main/java/com/torrent4j/net/peerwire/PeerWireHandler.java
Normal file
270
src/main/java/com/torrent4j/net/peerwire/PeerWireHandler.java
Normal file
@@ -0,0 +1,270 @@
|
||||
package com.torrent4j.net.peerwire;
|
||||
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.MessageEvent;
|
||||
import io.netty.channel.SimpleChannelHandler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Date;
|
||||
|
||||
import com.torrent4j.TorrentController;
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.model.TorrentPeerState.TorrentPeerChoking;
|
||||
import com.torrent4j.model.TorrentPeerState.TorrentPeerInterest;
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
import com.torrent4j.model.TorrentPieceBlock;
|
||||
import com.torrent4j.net.peerwire.codec.PeerWireFrameDecoder;
|
||||
import com.torrent4j.net.peerwire.codec.PeerWireFrameEncoder;
|
||||
import com.torrent4j.net.peerwire.codec.PeerWireMessageDecoder;
|
||||
import com.torrent4j.net.peerwire.codec.PeerWireMessageEncoder;
|
||||
import com.torrent4j.net.peerwire.messages.BitFieldMessage;
|
||||
import com.torrent4j.net.peerwire.messages.BlockMessage;
|
||||
import com.torrent4j.net.peerwire.messages.CancelMessage;
|
||||
import com.torrent4j.net.peerwire.messages.ChokeMessage;
|
||||
import com.torrent4j.net.peerwire.messages.HandshakeMessage;
|
||||
import com.torrent4j.net.peerwire.messages.HaveMessage;
|
||||
import com.torrent4j.net.peerwire.messages.InterestedMessage;
|
||||
import com.torrent4j.net.peerwire.messages.NotInterestedMessage;
|
||||
import com.torrent4j.net.peerwire.messages.RequestMessage;
|
||||
import com.torrent4j.net.peerwire.messages.UnchokeMessage;
|
||||
import com.torrent4j.net.peerwire.traffic.PeerTrafficShapingHandler;
|
||||
import com.torrent4j.net.peerwire.traffic.TorrentTrafficShapingHandler;
|
||||
import com.torrent4j.util.Hash;
|
||||
import com.torrent4j.util.HashType;
|
||||
|
||||
public class PeerWireHandler extends SimpleChannelHandler {
|
||||
private final TorrentController controller;
|
||||
private PeerWireProtocolPeer peer;
|
||||
|
||||
public PeerWireHandler(TorrentController controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
|
||||
throws Exception {
|
||||
try {
|
||||
final Object msg = e.getMessage();
|
||||
if (!(msg instanceof PeerWireMessage))
|
||||
return;
|
||||
if (msg instanceof HandshakeMessage) {
|
||||
final HandshakeMessage message = (HandshakeMessage) msg;
|
||||
|
||||
((PeerWireFrameDecoder) e.getChannel().getPipeline()
|
||||
.get("frame-decoder")).setHandshaked(true);
|
||||
((PeerWireMessageDecoder) e.getChannel().getPipeline()
|
||||
.get("message-decoder")).setHandshaked(true);
|
||||
|
||||
final Hash hash = new Hash(HashType.SHA1, message.torrentHash);
|
||||
final Torrent torrent = controller.findTorrent(hash);
|
||||
|
||||
if (torrent == null) {
|
||||
e.getChannel().disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
TorrentPeer peer = torrent.getSwarm().findPeer(
|
||||
(InetSocketAddress) e.getChannel().getRemoteAddress(),
|
||||
message.peerID);
|
||||
if (peer == null) {
|
||||
peer = new TorrentPeer(torrent);
|
||||
peer.setPeerID(message.peerID);
|
||||
peer.setAddress((InetSocketAddress) e.getChannel()
|
||||
.getRemoteAddress());
|
||||
}
|
||||
this.peer = (PeerWireProtocolPeer) peer.getProtocolPeer();
|
||||
|
||||
e.getChannel().getPipeline().get(PeerTrafficShapingHandler.class)
|
||||
.setPeer(peer);
|
||||
e.getChannel().getPipeline()
|
||||
.get(TorrentTrafficShapingHandler.class)
|
||||
.setTorrent(torrent);
|
||||
|
||||
peer.resetState();
|
||||
this.peer.getStrategy().getPeerStrategy()
|
||||
.peerConnected(torrent, peer);
|
||||
} else if (msg instanceof HaveMessage) {
|
||||
peer.getStrategy()
|
||||
.getPeerStrategy()
|
||||
.havePiece(
|
||||
peer.getTorrent(),
|
||||
peer.getTorrentPeer(),
|
||||
peer.getTorrent().getPiece(
|
||||
((HaveMessage) msg).pieceIndex));
|
||||
} else if (msg instanceof UnchokeMessage) {
|
||||
peer.getTorrentPeer().getState()
|
||||
.setRemoteChoked(TorrentPeerChoking.UNCHOKED);
|
||||
peer.getStrategy().getPeerStrategy()
|
||||
.unchoked(peer.getTorrent(), peer.getTorrentPeer());
|
||||
} else if (msg instanceof ChokeMessage) {
|
||||
peer.getTorrentPeer().getState()
|
||||
.setRemoteChoked(TorrentPeerChoking.CHOKED);
|
||||
peer.getStrategy().getPeerStrategy()
|
||||
.choked(peer.getTorrent(), peer.getTorrentPeer());
|
||||
} else if (msg instanceof InterestedMessage) {
|
||||
peer.getTorrentPeer().getState()
|
||||
.setRemoteInterest(TorrentPeerInterest.INTERESTED);
|
||||
peer.getStrategy().getPeerStrategy()
|
||||
.interested(peer.getTorrent(), peer.getTorrentPeer());
|
||||
} else if (msg instanceof NotInterestedMessage) {
|
||||
peer.getTorrentPeer().getState()
|
||||
.setRemoteInterest(TorrentPeerInterest.NOT_INTERESTED);
|
||||
peer.getStrategy()
|
||||
.getPeerStrategy()
|
||||
.notInterested(peer.getTorrent(), peer.getTorrentPeer());
|
||||
} else if (msg instanceof BitFieldMessage) {
|
||||
peer.getTorrentPeer().getPieces()
|
||||
.load(((BitFieldMessage) msg).bitSet);
|
||||
peer.getStrategy().getPeerStrategy()
|
||||
.bitField(peer.getTorrent(), peer.getTorrentPeer());
|
||||
} else if (msg instanceof RequestMessage) {
|
||||
final RequestMessage message = (RequestMessage) msg;
|
||||
|
||||
final TorrentPiece piece = peer.getTorrent().getPiece(
|
||||
message.pieceIndex);
|
||||
final TorrentPieceBlock block = piece.getBlock(message.begin,
|
||||
message.length);
|
||||
|
||||
peer.getTorrentPeer().getState().setUploadRequestedBlock(block);
|
||||
peer.getTorrentPeer().getState()
|
||||
.setUploadRequestedDate(new Date());
|
||||
|
||||
peer.getStrategy()
|
||||
.getUploadStrategy()
|
||||
.blockRequested(peer.getTorrent(), block,
|
||||
peer.getTorrentPeer());
|
||||
} else if (msg instanceof CancelMessage) {
|
||||
final CancelMessage message = (CancelMessage) msg;
|
||||
|
||||
final TorrentPiece piece = peer.getTorrent().getPiece(
|
||||
message.pieceIndex);
|
||||
final TorrentPieceBlock block = piece.getBlock(message.begin,
|
||||
message.length);
|
||||
|
||||
peer.getTorrentPeer().getState().setUploadRequestedBlock(null);
|
||||
peer.getTorrentPeer().getState().setUploadRequestedDate(null);
|
||||
|
||||
peer.getStrategy()
|
||||
.getUploadStrategy()
|
||||
.blockRequestCancelled(peer.getTorrent(), block,
|
||||
peer.getTorrentPeer());
|
||||
} else if (msg instanceof BlockMessage) {
|
||||
final BlockMessage message = (BlockMessage) msg;
|
||||
|
||||
final TorrentPiece piece = peer.getTorrent().getPiece(
|
||||
message.pieceIndex);
|
||||
final TorrentPieceBlock block = piece.getBlock(message.begin,
|
||||
message.data.remaining());
|
||||
|
||||
peer.getTorrentPeer().getState().setLastDownloadedBlock(block);
|
||||
peer.getTorrentPeer().getState()
|
||||
.setLastDownloadedBlockDate(new Date());
|
||||
|
||||
peer.getTorrentPeer().getState()
|
||||
.setDownloadRequestedBlock(null);
|
||||
peer.getTorrentPeer().getState().setDownloadRequestedDate(null);
|
||||
|
||||
controller.getStorage().write(piece.getTorrent(),
|
||||
block.getTorrentRange(), message.data);
|
||||
block.setDownloaded(true);
|
||||
if (piece.isDownloaded()) {
|
||||
final Hash pieceHash = controller.getStorage().checksum(
|
||||
piece);
|
||||
if (!piece.getHash().equals(pieceHash)) {
|
||||
piece.getTorrent()
|
||||
.getStrategy()
|
||||
.getDownloadStrategy()
|
||||
.pieceChecksumFailed(piece.getTorrent(), piece,
|
||||
peer.getTorrentPeer());
|
||||
} else {
|
||||
piece.getTorrent().getCompletedPieces().addPiece(piece);
|
||||
piece.getTorrent()
|
||||
.getStrategy()
|
||||
.getDownloadStrategy()
|
||||
.pieceComplete(piece.getTorrent(), piece,
|
||||
peer.getTorrentPeer());
|
||||
}
|
||||
} else {
|
||||
piece.getTorrent()
|
||||
.getStrategy()
|
||||
.getDownloadStrategy()
|
||||
.blockReceived(block.getTorrent(), block,
|
||||
peer.getTorrentPeer());
|
||||
}
|
||||
} else {
|
||||
System.out.println(msg);
|
||||
}
|
||||
} finally {
|
||||
ctx.sendUpstream(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRequested(ChannelHandlerContext ctx, MessageEvent e)
|
||||
throws Exception {
|
||||
try {
|
||||
final Object msg = e.getMessage();
|
||||
if (!(msg instanceof PeerWireMessage))
|
||||
return;
|
||||
if (msg instanceof HandshakeMessage) {
|
||||
e.getFuture().addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future)
|
||||
throws Exception {
|
||||
((PeerWireFrameEncoder) future.getChannel()
|
||||
.getPipeline().get("frame-encoder"))
|
||||
.setHandshaked(true);
|
||||
((PeerWireMessageEncoder) future.getChannel()
|
||||
.getPipeline().get("message-encoder"))
|
||||
.setHandshaked(true);
|
||||
}
|
||||
});
|
||||
} else if (msg instanceof BlockMessage) {
|
||||
final BlockMessage message = (BlockMessage) msg;
|
||||
|
||||
final TorrentPiece piece = peer.getTorrent().getPiece(
|
||||
message.pieceIndex);
|
||||
final TorrentPieceBlock block = piece.getBlock(message.begin,
|
||||
message.data.remaining());
|
||||
|
||||
peer.getTorrentPeer().getState().setLastUploadedBlock(block);
|
||||
peer.getTorrentPeer().getState()
|
||||
.setLastUploadedBlockDate(new Date());
|
||||
peer.getTorrentPeer().getState().setUploadRequestedBlock(null);
|
||||
peer.getTorrentPeer().getState().setUploadRequestedDate(null);
|
||||
} else if (msg instanceof RequestMessage) {
|
||||
final RequestMessage message = (RequestMessage) msg;
|
||||
|
||||
final TorrentPiece piece = peer.getTorrent().getPiece(
|
||||
message.pieceIndex);
|
||||
final TorrentPieceBlock block = piece.getBlock(message.begin,
|
||||
message.length);
|
||||
|
||||
peer.getTorrentPeer().getState()
|
||||
.setDownloadRequestedBlock(block);
|
||||
peer.getTorrentPeer().getState()
|
||||
.setDownloadRequestedDate(new Date());
|
||||
} else if (msg instanceof CancelMessage) {
|
||||
peer.getTorrentPeer().getState()
|
||||
.setDownloadRequestedBlock(null);
|
||||
peer.getTorrentPeer().getState().setDownloadRequestedDate(null);
|
||||
}
|
||||
} finally {
|
||||
ctx.sendDownstream(e);
|
||||
}
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent
|
||||
// e)
|
||||
// throws Exception {
|
||||
// try {
|
||||
// peer = new PeerWireProtocolPeer(e.getChannel());
|
||||
// } finally {
|
||||
// ctx.sendUpstream(e);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.torrent4j.net.peerwire;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
public interface PeerWireMessage {
|
||||
void write(ChannelBuffer buffer);
|
||||
|
||||
void read(ChannelBuffer buffer);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.torrent4j.net.peerwire;
|
||||
|
||||
import static io.netty.channel.Channels.pipeline;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelPipelineFactory;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.torrent4j.TorrentController;
|
||||
import com.torrent4j.net.peerwire.codec.PeerWireFrameDecoder;
|
||||
import com.torrent4j.net.peerwire.codec.PeerWireFrameEncoder;
|
||||
import com.torrent4j.net.peerwire.codec.PeerWireMessageDecoder;
|
||||
import com.torrent4j.net.peerwire.codec.PeerWireMessageEncoder;
|
||||
import com.torrent4j.net.peerwire.traffic.TorrentTrafficShapingHandler;
|
||||
import com.torrent4j.net.peerwire.traffic.PeerTrafficShapingHandler;
|
||||
|
||||
public class PeerWirePipelineFactory implements ChannelPipelineFactory {
|
||||
private final TorrentController controller;
|
||||
private final Executor executor;
|
||||
|
||||
public PeerWirePipelineFactory(TorrentController controller,
|
||||
Executor executor) {
|
||||
this.controller = controller;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline getPipeline() throws Exception {
|
||||
final ChannelPipeline p = pipeline();
|
||||
|
||||
p.addLast("torrent-shaper", new TorrentTrafficShapingHandler(executor));
|
||||
p.addLast("traffic-shaper", new PeerTrafficShapingHandler(executor));
|
||||
|
||||
p.addLast("frame-decoder", new PeerWireFrameDecoder());
|
||||
p.addLast("frame-encoder", new PeerWireFrameEncoder());
|
||||
|
||||
p.addLast("message-decoder", new PeerWireMessageDecoder());
|
||||
p.addLast("message-encoder", new PeerWireMessageEncoder());
|
||||
|
||||
// p.addLast("logging", new LoggingHandler(InternalLogLevel.WARN));
|
||||
|
||||
p.addLast("handler", new PeerWireHandler(controller));
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.torrent4j.net.peerwire;
|
||||
|
||||
import io.netty.bootstrap.ClientBootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.socket.nio.NioClientSocketChannelFactory;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.torrent4j.TorrentController;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.net.TorrentProtocol;
|
||||
|
||||
public class PeerWireProtocol implements TorrentProtocol {
|
||||
private final Executor threadPool = Executors.newCachedThreadPool();
|
||||
|
||||
private final ServerBootstrap serverBootstrap = new ServerBootstrap(
|
||||
new NioServerSocketChannelFactory(threadPool, threadPool));
|
||||
private final ClientBootstrap clientBootstrap = new ClientBootstrap(
|
||||
new NioClientSocketChannelFactory(threadPool, threadPool));
|
||||
|
||||
private Channel serverChannel;
|
||||
|
||||
@Override
|
||||
public void start(TorrentController controller, int listenPort) {
|
||||
serverBootstrap.setPipelineFactory(new PeerWirePipelineFactory(
|
||||
controller, threadPool));
|
||||
clientBootstrap.setPipelineFactory(new PeerWirePipelineFactory(
|
||||
controller, threadPool));
|
||||
|
||||
try {
|
||||
serverChannel = serverBootstrap.bind(new InetSocketAddress(
|
||||
Inet4Address.getByName("0.0.0.0"), listenPort));
|
||||
} catch (UnknownHostException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
clientBootstrap.releaseExternalResources();
|
||||
serverChannel.close();
|
||||
serverBootstrap.releaseExternalResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect(TorrentPeer peer) {
|
||||
final ChannelFuture future = clientBootstrap.connect(peer.getAddress())
|
||||
.awaitUninterruptibly();
|
||||
if (future.isSuccess()) {
|
||||
final PeerWireProtocolPeer protocolPeer = new PeerWireProtocolPeer(
|
||||
future.getChannel());
|
||||
protocolPeer.setTorrentPeer(peer);
|
||||
peer.setProtocolPeer(protocolPeer);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.torrent4j.net.peerwire;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.BitSet;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.net.TorrentProtocolPeer;
|
||||
import com.torrent4j.net.peerwire.messages.BitFieldMessage;
|
||||
import com.torrent4j.net.peerwire.messages.BlockMessage;
|
||||
import com.torrent4j.net.peerwire.messages.CancelMessage;
|
||||
import com.torrent4j.net.peerwire.messages.ChokeMessage;
|
||||
import com.torrent4j.net.peerwire.messages.HandshakeMessage;
|
||||
import com.torrent4j.net.peerwire.messages.HaveMessage;
|
||||
import com.torrent4j.net.peerwire.messages.InterestedMessage;
|
||||
import com.torrent4j.net.peerwire.messages.KeepAliveMessage;
|
||||
import com.torrent4j.net.peerwire.messages.NotInterestedMessage;
|
||||
import com.torrent4j.net.peerwire.messages.PortMessage;
|
||||
import com.torrent4j.net.peerwire.messages.RequestMessage;
|
||||
import com.torrent4j.net.peerwire.messages.UnchokeMessage;
|
||||
import com.torrent4j.strategy.TorrentStrategy;
|
||||
|
||||
public class PeerWireProtocolPeer implements TorrentProtocolPeer {
|
||||
private final Channel channel;
|
||||
private TorrentPeer peer;
|
||||
|
||||
public PeerWireProtocolPeer(Channel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect() {
|
||||
return channel.connect(channel.getRemoteAddress())
|
||||
.awaitUninterruptibly().isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return channel.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnect() {
|
||||
return channel.disconnect().awaitUninterruptibly().isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handshake(byte[] torrentHash, String peerID) {
|
||||
write(new HandshakeMessage(torrentHash, peerID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestBlock(int pieceIndex, int start, int length) {
|
||||
write(new RequestMessage(pieceIndex, start, length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelRequestedBlock(int pieceIndex, int start, int length) {
|
||||
write(new CancelMessage(pieceIndex, start, length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendBlock(int pieceIndex, int start, ByteBuffer data) {
|
||||
write(new BlockMessage(pieceIndex, start, data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bitField(BitSet bitSet) {
|
||||
write(new BitFieldMessage(bitSet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void have(int pieceIndex) {
|
||||
write(new HaveMessage(pieceIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void choke() {
|
||||
write(new ChokeMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unchoke() {
|
||||
write(new UnchokeMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interested() {
|
||||
write(new InterestedMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notInterested() {
|
||||
write(new NotInterestedMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void port(int dhtPort) {
|
||||
write(new PortMessage(dhtPort));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keepAlive() {
|
||||
write(new KeepAliveMessage());
|
||||
}
|
||||
|
||||
public ChannelFuture write(PeerWireMessage message) {
|
||||
return channel.write(message);
|
||||
}
|
||||
|
||||
public TorrentPeer getTorrentPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
public void setTorrentPeer(TorrentPeer peer) {
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return peer.getTorrent();
|
||||
}
|
||||
|
||||
public TorrentStrategy getStrategy() {
|
||||
return peer.getTorrent().getStrategy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.torrent4j.net.peerwire.codec;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.frame.FrameDecoder;
|
||||
|
||||
public class PeerWireFrameDecoder extends FrameDecoder {
|
||||
private boolean handshaked = false;
|
||||
|
||||
@Override
|
||||
protected Object decode(ChannelHandlerContext ctx, Channel channel,
|
||||
ChannelBuffer buffer) throws Exception {
|
||||
if (!handshaked) {
|
||||
int pos = buffer.readerIndex();
|
||||
buffer.skipBytes(68);
|
||||
return buffer.copy(pos, 68);
|
||||
}
|
||||
|
||||
if (buffer.readableBytes() < 4)
|
||||
return null;
|
||||
|
||||
int pos = buffer.readerIndex();
|
||||
final int len = buffer.readInt();
|
||||
if (buffer.readableBytes() >= len) {
|
||||
try {
|
||||
return buffer.slice(buffer.readerIndex(), len);
|
||||
} finally {
|
||||
buffer.skipBytes(len);
|
||||
}
|
||||
} else {
|
||||
buffer.readerIndex(pos);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setHandshaked(boolean handshaked) {
|
||||
this.handshaked = handshaked;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.torrent4j.net.peerwire.codec;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.oneone.OneToOneEncoder;
|
||||
|
||||
public class PeerWireFrameEncoder extends OneToOneEncoder {
|
||||
private boolean handshaked = false;
|
||||
|
||||
@Override
|
||||
protected Object encode(ChannelHandlerContext ctx, Channel channel,
|
||||
Object msg) throws Exception {
|
||||
if (!(msg instanceof ChannelBuffer) || !handshaked)
|
||||
return msg;
|
||||
final ChannelBuffer buffer = (ChannelBuffer) msg;
|
||||
|
||||
buffer.readerIndex(0);
|
||||
if (handshaked) {
|
||||
final int len = buffer.readableBytes() - 4;
|
||||
buffer.setInt(0, len);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void setHandshaked(boolean handshaked) {
|
||||
this.handshaked = handshaked;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.torrent4j.net.peerwire.codec;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.oneone.OneToOneDecoder;
|
||||
|
||||
import com.torrent4j.net.peerwire.PeerWireMessage;
|
||||
import com.torrent4j.net.peerwire.messages.BitFieldMessage;
|
||||
import com.torrent4j.net.peerwire.messages.BlockMessage;
|
||||
import com.torrent4j.net.peerwire.messages.CancelMessage;
|
||||
import com.torrent4j.net.peerwire.messages.ChokeMessage;
|
||||
import com.torrent4j.net.peerwire.messages.HandshakeMessage;
|
||||
import com.torrent4j.net.peerwire.messages.HaveMessage;
|
||||
import com.torrent4j.net.peerwire.messages.InterestedMessage;
|
||||
import com.torrent4j.net.peerwire.messages.KeepAliveMessage;
|
||||
import com.torrent4j.net.peerwire.messages.NotInterestedMessage;
|
||||
import com.torrent4j.net.peerwire.messages.PortMessage;
|
||||
import com.torrent4j.net.peerwire.messages.RequestMessage;
|
||||
import com.torrent4j.net.peerwire.messages.UnchokeMessage;
|
||||
|
||||
public class PeerWireMessageDecoder extends OneToOneDecoder {
|
||||
private boolean handshaked = false;
|
||||
|
||||
@Override
|
||||
protected Object decode(ChannelHandlerContext ctx, Channel channel,
|
||||
Object msg) throws Exception {
|
||||
if (!(msg instanceof ChannelBuffer))
|
||||
return msg;
|
||||
final ChannelBuffer buffer = (ChannelBuffer) msg;
|
||||
|
||||
if (!handshaked) {
|
||||
final HandshakeMessage message = new HandshakeMessage();
|
||||
message.read(buffer);
|
||||
|
||||
return message;
|
||||
} else {
|
||||
if(buffer.readableBytes() == 0)
|
||||
return new KeepAliveMessage();
|
||||
|
||||
final byte opcode = buffer.readByte();
|
||||
final PeerWireMessage message;
|
||||
switch (opcode) {
|
||||
case CancelMessage.MESSAGE_ID:
|
||||
message = new CancelMessage();
|
||||
break;
|
||||
case BitFieldMessage.MESSAGE_ID:
|
||||
message = new BitFieldMessage();
|
||||
break;
|
||||
case ChokeMessage.MESSAGE_ID:
|
||||
message = new ChokeMessage();
|
||||
break;
|
||||
case HaveMessage.MESSAGE_ID:
|
||||
message = new HaveMessage();
|
||||
break;
|
||||
case InterestedMessage.MESSAGE_ID:
|
||||
message = new InterestedMessage();
|
||||
break;
|
||||
case NotInterestedMessage.MESSAGE_ID:
|
||||
message = new NotInterestedMessage();
|
||||
break;
|
||||
case BlockMessage.MESSAGE_ID:
|
||||
message = new BlockMessage();
|
||||
break;
|
||||
case PortMessage.MESSAGE_ID:
|
||||
message = new PortMessage();
|
||||
break;
|
||||
case RequestMessage.MESSAGE_ID:
|
||||
message = new RequestMessage();
|
||||
break;
|
||||
case UnchokeMessage.MESSAGE_ID:
|
||||
message = new UnchokeMessage();
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
message.read(buffer);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
public void setHandshaked(boolean handshaked) {
|
||||
this.handshaked = handshaked;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.torrent4j.net.peerwire.codec;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.oneone.OneToOneEncoder;
|
||||
|
||||
import com.torrent4j.net.peerwire.PeerWireMessage;
|
||||
import com.torrent4j.net.peerwire.messages.KeepAliveMessage;
|
||||
|
||||
public class PeerWireMessageEncoder extends OneToOneEncoder {
|
||||
private boolean handshaked = false;
|
||||
|
||||
@Override
|
||||
protected Object encode(ChannelHandlerContext ctx, Channel channel,
|
||||
Object msg) throws Exception {
|
||||
if (!(msg instanceof PeerWireMessage))
|
||||
return msg;
|
||||
final PeerWireMessage message = (PeerWireMessage) msg;
|
||||
final ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
|
||||
|
||||
if (handshaked && !(message instanceof KeepAliveMessage))
|
||||
buffer.writeInt(0x00);
|
||||
message.write(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void setHandshaked(boolean handshaked) {
|
||||
this.handshaked = handshaked;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
|
||||
|
||||
public class BitFieldMessage extends AbstractPeerWireMessage {
|
||||
public static final int MESSAGE_ID = 0x05;
|
||||
|
||||
public BitSet bitSet;
|
||||
|
||||
public BitFieldMessage() {
|
||||
super(MESSAGE_ID);
|
||||
}
|
||||
|
||||
public BitFieldMessage(BitSet bitSet) {
|
||||
super(MESSAGE_ID);
|
||||
this.bitSet = bitSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeImpl(ChannelBuffer buffer) {
|
||||
for (int i = 0; i < bitSet.size();) {
|
||||
byte data = 0;
|
||||
for (int j = 128; i < bitSet.size() && j > 0; j >>= 1, i++) {
|
||||
if (bitSet.get(i)) {
|
||||
data |= j;
|
||||
}
|
||||
}
|
||||
buffer.writeByte(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readImpl(ChannelBuffer buffer) {
|
||||
bitSet = new BitSet();
|
||||
int i = 0;
|
||||
while (buffer.readable()) {
|
||||
byte b = buffer.readByte();
|
||||
for (int j = 128; j > 0; j >>= 1) {
|
||||
bitSet.set(i++, (b & j) != 0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BITFIELD [bitSet=" + bitSet + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
|
||||
|
||||
public class BlockMessage extends AbstractPeerWireMessage {
|
||||
public static final int MESSAGE_ID = 0x07;
|
||||
|
||||
public int pieceIndex;
|
||||
public int begin;
|
||||
public ByteBuffer data;
|
||||
|
||||
public BlockMessage() {
|
||||
super(MESSAGE_ID);
|
||||
}
|
||||
|
||||
public BlockMessage(int pieceIndex, int begin, ByteBuffer data) {
|
||||
super(MESSAGE_ID);
|
||||
this.pieceIndex = pieceIndex;
|
||||
this.begin = begin;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeImpl(ChannelBuffer buffer) {
|
||||
buffer.writeInt(pieceIndex);
|
||||
buffer.writeInt(begin);
|
||||
buffer.writeBytes(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readImpl(ChannelBuffer buffer) {
|
||||
pieceIndex = buffer.readInt();
|
||||
begin = buffer.readInt();
|
||||
data = buffer.readBytes(buffer.readableBytes()).toByteBuffer();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BLOCK [pieceIndex=" + pieceIndex + ", begin=" + begin
|
||||
+ ", data=" + data + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
|
||||
|
||||
public class CancelMessage extends AbstractPeerWireMessage {
|
||||
public static final int MESSAGE_ID = 0x08;
|
||||
|
||||
public int pieceIndex;
|
||||
public int begin;
|
||||
public int length;
|
||||
|
||||
public CancelMessage() {
|
||||
super(MESSAGE_ID);
|
||||
}
|
||||
|
||||
public CancelMessage(int pieceIndex, int begin, int length) {
|
||||
super(MESSAGE_ID);
|
||||
this.pieceIndex = pieceIndex;
|
||||
this.begin = begin;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeImpl(ChannelBuffer buffer) {
|
||||
buffer.writeInt(pieceIndex);
|
||||
buffer.writeInt(begin);
|
||||
buffer.writeInt(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readImpl(ChannelBuffer buffer) {
|
||||
pieceIndex = buffer.readInt();
|
||||
begin = buffer.readInt();
|
||||
length = buffer.readInt();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CANCEL [pieceIndex=" + pieceIndex + ", begin=" + begin
|
||||
+ ", length=" + length + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
|
||||
|
||||
public class ChokeMessage extends AbstractPeerWireMessage {
|
||||
public static final int MESSAGE_ID = 0x00;
|
||||
|
||||
public ChokeMessage() {
|
||||
super(MESSAGE_ID);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CHOKE []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.torrent4j.net.peerwire.PeerWireMessage;
|
||||
|
||||
public class HandshakeMessage implements PeerWireMessage {
|
||||
public int protocolStringLength = 19;
|
||||
public String protocolString = "BitTorrent protocol";
|
||||
public long reserved = 0;
|
||||
public byte[] torrentHash;
|
||||
public String peerID;
|
||||
|
||||
public HandshakeMessage() {
|
||||
}
|
||||
|
||||
public HandshakeMessage(byte[] torrentHash, String peerID) {
|
||||
this.torrentHash = torrentHash;
|
||||
this.peerID = peerID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) {
|
||||
buffer.writeByte(protocolStringLength);
|
||||
buffer.writeBytes(protocolString.getBytes());
|
||||
buffer.writeLong(reserved);
|
||||
buffer.writeBytes(torrentHash, 0, 20);
|
||||
buffer.writeBytes(peerID.getBytes(), 0, 20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) {
|
||||
protocolStringLength = buffer.readByte();
|
||||
protocolString = buffer.readBytes(protocolStringLength).toString();
|
||||
reserved = buffer.readLong();
|
||||
torrentHash = buffer.readBytes(20).array();
|
||||
peerID = buffer.readBytes(20).toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HANDSHAKE [protocolStringLength=" + protocolStringLength
|
||||
+ ", protocolString=" + protocolString + ", reserved="
|
||||
+ reserved + ", torrentHash=" + Arrays.toString(torrentHash)
|
||||
+ ", peerID=" + peerID + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
|
||||
|
||||
public class HaveMessage extends AbstractPeerWireMessage {
|
||||
public static final int MESSAGE_ID = 0x04;
|
||||
|
||||
public int pieceIndex;
|
||||
|
||||
public HaveMessage() {
|
||||
super(MESSAGE_ID);
|
||||
}
|
||||
|
||||
public HaveMessage(int pieceIndex) {
|
||||
super(MESSAGE_ID);
|
||||
this.pieceIndex = pieceIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeImpl(ChannelBuffer buffer) {
|
||||
buffer.writeInt(pieceIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readImpl(ChannelBuffer buffer) {
|
||||
pieceIndex = buffer.readInt();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HAVE [pieceIndex=" + pieceIndex + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
|
||||
|
||||
public class InterestedMessage extends AbstractPeerWireMessage {
|
||||
public static final int MESSAGE_ID = 0x02;
|
||||
|
||||
public InterestedMessage() {
|
||||
super(MESSAGE_ID);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "INTERESTED []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
import com.torrent4j.net.peerwire.PeerWireMessage;
|
||||
|
||||
public class KeepAliveMessage implements PeerWireMessage {
|
||||
public KeepAliveMessage() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) {
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KEEP_ALIVE []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
|
||||
|
||||
public class NotInterestedMessage extends AbstractPeerWireMessage {
|
||||
public static final int MESSAGE_ID = 0x03;
|
||||
|
||||
public NotInterestedMessage() {
|
||||
super(MESSAGE_ID);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NOT_INTERESTED []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
|
||||
|
||||
public class PortMessage extends AbstractPeerWireMessage {
|
||||
public static final int MESSAGE_ID = 0x09;
|
||||
|
||||
public int listenPort;
|
||||
|
||||
public PortMessage() {
|
||||
super(MESSAGE_ID);
|
||||
}
|
||||
|
||||
public PortMessage(int listenPort) {
|
||||
super(MESSAGE_ID);
|
||||
this.listenPort = listenPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeImpl(ChannelBuffer buffer) {
|
||||
buffer.writeInt(listenPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readImpl(ChannelBuffer buffer) {
|
||||
listenPort = buffer.readInt();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PORT [listenPort=" + listenPort + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
|
||||
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
|
||||
|
||||
public class RequestMessage extends AbstractPeerWireMessage {
|
||||
public static final int MESSAGE_ID = 0x06;
|
||||
|
||||
public int pieceIndex;
|
||||
public int begin;
|
||||
public int length;
|
||||
|
||||
public RequestMessage() {
|
||||
super(MESSAGE_ID);
|
||||
}
|
||||
|
||||
public RequestMessage(int pieceIndex, int begin, int length) {
|
||||
super(MESSAGE_ID);
|
||||
this.pieceIndex = pieceIndex;
|
||||
this.begin = begin;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeImpl(ChannelBuffer buffer) {
|
||||
buffer.writeInt(pieceIndex);
|
||||
buffer.writeInt(begin);
|
||||
buffer.writeInt(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readImpl(ChannelBuffer buffer) {
|
||||
pieceIndex = buffer.readInt();
|
||||
begin = buffer.readInt();
|
||||
length = buffer.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "REQUEST [pieceIndex=" + pieceIndex + ", begin=" + begin
|
||||
+ ", length=" + length + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.torrent4j.net.peerwire.messages;
|
||||
|
||||
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
|
||||
|
||||
public class UnchokeMessage extends AbstractPeerWireMessage {
|
||||
public static final int MESSAGE_ID = 0x01;
|
||||
|
||||
public UnchokeMessage() {
|
||||
super(MESSAGE_ID);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UNCHOKE []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.torrent4j.net.peerwire.traffic;
|
||||
|
||||
import io.netty.handler.traffic.ChannelTrafficShapingHandler;
|
||||
import io.netty.handler.traffic.TrafficCounter;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.model.TorrentPeerTrafficControl;
|
||||
|
||||
public class PeerTrafficShapingHandler extends ChannelTrafficShapingHandler {
|
||||
private long writeLimit;
|
||||
private long readLimit;
|
||||
|
||||
private TorrentPeer peer;
|
||||
|
||||
public PeerTrafficShapingHandler(Executor executor) {
|
||||
super(executor, 0, 0);
|
||||
}
|
||||
|
||||
private void reconfigure() {
|
||||
if (peer == null)
|
||||
return;
|
||||
final TorrentPeerTrafficControl peerTraffic = peer.getTrafficControl();
|
||||
long readLimit = peerTraffic.getDownloadSpeedLimit();
|
||||
long writeLimit = peerTraffic.getUploadSpeedLimit();
|
||||
if (readLimit != this.readLimit || writeLimit != this.writeLimit) {
|
||||
this.writeLimit = writeLimit;
|
||||
this.readLimit = readLimit;
|
||||
configure(writeLimit, readLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doAccounting(TrafficCounter counter) {
|
||||
if (peer == null)
|
||||
return;
|
||||
reconfigure();
|
||||
peer.getTrafficControl().setCurrentDownloadSpeed(
|
||||
counter.getLastReadThroughput());
|
||||
peer.getTrafficControl().setCurrentUploadSpeed(
|
||||
counter.getLastWriteThroughput());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the peer
|
||||
*/
|
||||
public TorrentPeer getPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param peer
|
||||
* the peer to set
|
||||
*/
|
||||
public void setPeer(TorrentPeer peer) {
|
||||
this.peer = peer;
|
||||
reconfigure();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.torrent4j.net.peerwire.traffic;
|
||||
|
||||
import io.netty.handler.traffic.GlobalTrafficShapingHandler;
|
||||
import io.netty.handler.traffic.TrafficCounter;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentTrafficControl;
|
||||
|
||||
public class TorrentTrafficShapingHandler extends GlobalTrafficShapingHandler {
|
||||
private Torrent torrent;
|
||||
|
||||
private long writeLimit;
|
||||
private long readLimit;
|
||||
|
||||
public TorrentTrafficShapingHandler(Executor executor) {
|
||||
super(executor, 0, 0);
|
||||
}
|
||||
|
||||
private void reconfigure() {
|
||||
if (torrent == null)
|
||||
return;
|
||||
final TorrentTrafficControl traffic = torrent.getTrafficControl();
|
||||
long readLimit = traffic.getDownloadSpeedLimit();
|
||||
long writeLimit = traffic.getUploadSpeedLimit();
|
||||
if (readLimit != this.readLimit || writeLimit != this.writeLimit) {
|
||||
this.writeLimit = writeLimit;
|
||||
this.readLimit = readLimit;
|
||||
configure(writeLimit, readLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doAccounting(TrafficCounter counter) {
|
||||
if (torrent == null)
|
||||
return;
|
||||
reconfigure();
|
||||
torrent.getTrafficControl().setCurrentDownloadSpeed(
|
||||
counter.getLastReadThroughput());
|
||||
torrent.getTrafficControl().setCurrentUploadSpeed(
|
||||
counter.getLastWriteThroughput());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the torrent
|
||||
*/
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param torrent
|
||||
* the torrent to set
|
||||
*/
|
||||
public void setTorrent(Torrent torrent) {
|
||||
this.torrent = torrent;
|
||||
reconfigure();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.torrent4j.storage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
import com.torrent4j.util.Hash;
|
||||
|
||||
/**
|
||||
* Abstract {@link TorrentStorage} that implements universal that methods that
|
||||
* very likely don't need to be override by implementations.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public abstract class AbstractTorrentStorage implements TorrentStorage {
|
||||
@Override
|
||||
public Hash checksum(TorrentPiece piece) throws IOException {
|
||||
final ByteBuffer buffer = read(piece.getTorrent(),
|
||||
piece.getTorrentRange());
|
||||
if (buffer == null)
|
||||
return null;
|
||||
return new Hash(piece.getHash().getType(), piece.getHash().getType()
|
||||
.hash(buffer));
|
||||
}
|
||||
}
|
||||
101
src/main/java/com/torrent4j/storage/FileAwareTorrentStorage.java
Normal file
101
src/main/java/com/torrent4j/storage/FileAwareTorrentStorage.java
Normal file
@@ -0,0 +1,101 @@
|
||||
package com.torrent4j.storage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentFile;
|
||||
import com.torrent4j.util.Range;
|
||||
|
||||
/**
|
||||
* Provides an abstraction to storage implementations that allows data to be
|
||||
* stored into multiple files, respecting data stored into the original .torrent
|
||||
* meta data file. {@link #write(Torrent, Range, ByteBuffer)} and
|
||||
* {@link #read(Torrent, Range)} detect the file (or files) contained in the
|
||||
* requested range and for each file that needs to be written or redden, a call
|
||||
* to {@link #write(TorrentFile, Range, ByteBuffer)} and
|
||||
* {@link #read(TorrentFile, Range, ByteBuffer)} is made. On all cases, the same
|
||||
* buffer is sent for several calls.
|
||||
* <p>
|
||||
* If any error occur while reading any of the files, the whole reading process
|
||||
* fails.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*
|
||||
*/
|
||||
public abstract class FileAwareTorrentStorage extends AbstractTorrentStorage
|
||||
implements TorrentStorage {
|
||||
@Override
|
||||
public final boolean write(Torrent torrent, Range dataRange, ByteBuffer data)
|
||||
throws IOException {
|
||||
final List<TorrentFile> files = torrent.getFiles(dataRange);
|
||||
for (final TorrentFile file : files) {
|
||||
final Range rangeOnFile = file.getTorrentRange().intersection(
|
||||
dataRange);
|
||||
final Range range = file.getFileRange().intersection(
|
||||
Range.getRangeByLength(
|
||||
rangeOnFile.getStart() - file.getOffset(),
|
||||
rangeOnFile.getLength()));
|
||||
data.limit((int) (data.position() + range.getLength()));
|
||||
if (!this.write(file, range, data))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuffer read(Torrent torrent, Range dataRange)
|
||||
throws IOException {
|
||||
final List<TorrentFile> files = torrent.getFiles(dataRange);
|
||||
final ByteBuffer data = ByteBuffer
|
||||
.allocate((int) dataRange.getLength());
|
||||
for (final TorrentFile file : files) {
|
||||
final Range rangeOnFile = file.getTorrentRange().intersection(
|
||||
dataRange);
|
||||
final Range range = file.getFileRange().intersection(
|
||||
Range.getRangeByLength(
|
||||
rangeOnFile.getStart() - file.getOffset(),
|
||||
rangeOnFile.getLength()));
|
||||
data.limit((int) (data.position() + range.getLength()));
|
||||
if (!this.read(file, range, data))
|
||||
return null;
|
||||
}
|
||||
data.flip();
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes <code>data</code> into the given <code>file</code> at the
|
||||
* requested <code>range</code>.
|
||||
*
|
||||
* @param file
|
||||
* the file to write the data to
|
||||
* @param range
|
||||
* the range to write the data to
|
||||
* @param data
|
||||
* the data to be written
|
||||
* @return <code>true</code> if write was successful
|
||||
* @throws IOException
|
||||
* if any exception occur while writing data
|
||||
*/
|
||||
protected abstract boolean write(TorrentFile file, Range range,
|
||||
ByteBuffer data) throws IOException;
|
||||
|
||||
/**
|
||||
* Reads <code>data</code> from the given <code>file</code> in the requested
|
||||
* <code>range</code>.
|
||||
*
|
||||
* @param file
|
||||
* the file to read the data from
|
||||
* @param range
|
||||
* the range to read the data from
|
||||
* @param data
|
||||
* the data to be redden
|
||||
* @return <code>true</code> if read was successful
|
||||
* @throws IOException
|
||||
* if any exception occur while reading data
|
||||
*/
|
||||
protected abstract boolean read(TorrentFile file, Range range,
|
||||
ByteBuffer data) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.torrent4j.storage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.util.Range;
|
||||
|
||||
/**
|
||||
* An simple {@link TorrentStorage} implementation that stores data into huge
|
||||
* in-memory direct buffers. Please note that though it provides really fast
|
||||
* write and reading, it should not be used in real world implementations.
|
||||
* Storing data in-memory consumes a lot of memory and may cause the JVM to be
|
||||
* terminated by some OSes.
|
||||
* <p>
|
||||
* <b>This class is intended for benchmarking and testing!</b>
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class InMemoryTorrentStorage extends AbstractTorrentStorage implements
|
||||
TorrentStorage {
|
||||
/**
|
||||
* The map containing all buffers for all active torrents
|
||||
*/
|
||||
private final Map<Torrent, ByteBuffer> buffers = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean write(Torrent torrent, Range range, ByteBuffer data)
|
||||
throws IOException {
|
||||
final ByteBuffer buffer = getBuffer(torrent);
|
||||
synchronized (buffer) {
|
||||
buffer.position((int) range.getStart());
|
||||
buffer.put(data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer read(Torrent torrent, Range range) throws IOException {
|
||||
final ByteBuffer store = getBuffer(torrent);
|
||||
synchronized (store) {
|
||||
return (ByteBuffer) ((ByteBuffer) store.position((int) range
|
||||
.getStart())).slice().limit((int) range.getLength());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to return an existing buffer for the <code>torrent</code>. If none
|
||||
* is found, creates a new buffer and returns it.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent instance
|
||||
* @return an {@link ByteBuffer} allocated with enough size to store all the
|
||||
* torrent data into it
|
||||
*/
|
||||
private ByteBuffer getBuffer(Torrent torrent) {
|
||||
ByteBuffer buffer = buffers.get(torrent);
|
||||
if (buffer == null) {
|
||||
buffer = ByteBuffer.allocateDirect((int) torrent.getTorrentSize());
|
||||
buffers.put(torrent, buffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
98
src/main/java/com/torrent4j/storage/NIOTorrentStorage.java
Normal file
98
src/main/java/com/torrent4j/storage/NIOTorrentStorage.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package com.torrent4j.storage;
|
||||
|
||||
import static java.nio.file.StandardOpenOption.CREATE;
|
||||
import static java.nio.file.StandardOpenOption.READ;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import com.torrent4j.model.TorrentFile;
|
||||
import com.torrent4j.util.Range;
|
||||
|
||||
/**
|
||||
* This {@link TorrentStorage} instance implements
|
||||
* {@link FileAwareTorrentStorage}, and thus, stores data into separated files
|
||||
* backed by an NIO.2 {@link SeekableByteChannel} available on Java 7. The
|
||||
* implementation tries to respect as much as possible from the original file
|
||||
* and folder structure, however, if any invalid name is found, beware that this
|
||||
* implementation might override it.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class NIOTorrentStorage extends FileAwareTorrentStorage {
|
||||
/**
|
||||
* The place where data files are stored
|
||||
*/
|
||||
private final Path root;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param root
|
||||
* the place where torrent data is stored
|
||||
*/
|
||||
public NIOTorrentStorage(Path root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance which stores data into the current working
|
||||
* directory
|
||||
*/
|
||||
public NIOTorrentStorage() {
|
||||
this(Paths.get(".").toAbsolutePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized boolean write(TorrentFile file, Range range,
|
||||
ByteBuffer data) throws IOException {
|
||||
try (final SeekableByteChannel channel = openChannel(file, CREATE,
|
||||
WRITE)) {
|
||||
if (channel == null)
|
||||
return false;
|
||||
channel.position(range.getStart());
|
||||
return channel.write(data) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized boolean read(TorrentFile file, Range range,
|
||||
ByteBuffer data) throws IOException {
|
||||
try (final SeekableByteChannel channel = openChannel(file, READ)) {
|
||||
if (channel == null)
|
||||
return false;
|
||||
channel.position(range.getStart());
|
||||
return channel.read(data) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new channel ready for writing or reading (defines by
|
||||
* <code>modes</code>) data into the torrent <code>file</code>.
|
||||
*
|
||||
* @param file
|
||||
* the torrent file
|
||||
* @param modes
|
||||
* the file open mode
|
||||
* @return an {@link SeekableByteChannel}
|
||||
* @throws IOException
|
||||
* if any error is thrown by NIO.2
|
||||
*/
|
||||
private SeekableByteChannel openChannel(TorrentFile file,
|
||||
OpenOption... modes) throws IOException {
|
||||
final Path filePath = root.resolve(file.getFileName());
|
||||
try {
|
||||
SeekableByteChannel channel = Files.newByteChannel(filePath, modes);
|
||||
return channel;
|
||||
} catch (NoSuchFileException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/main/java/com/torrent4j/storage/TorrentStorage.java
Normal file
61
src/main/java/com/torrent4j/storage/TorrentStorage.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.torrent4j.storage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
import com.torrent4j.util.Hash;
|
||||
import com.torrent4j.util.Range;
|
||||
|
||||
/**
|
||||
* Provides an easy interface to access torrent data in an linear way. All
|
||||
* torrent data is accessed by an single addressing, starting from the first
|
||||
* piece to that last one. Implementations can create abstractions to support
|
||||
* files (see {@link FileAwareTorrentStorage}) or can store data into a huge
|
||||
* in-memory buffer (see {@link InMemoryTorrentStorage}).
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public interface TorrentStorage {
|
||||
/**
|
||||
* Writes data into the underlying storage
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param torrentRange
|
||||
* the range in which the data is located
|
||||
* @param data
|
||||
* the data itself
|
||||
* @return <code>true</code> if the write was successful
|
||||
* @throws IOException
|
||||
* if any error occur while writing
|
||||
*/
|
||||
boolean write(Torrent torrent, Range torrentRange, ByteBuffer data)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Reads data from the underlying storage
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param torrentRange
|
||||
* the range in which the data should be read from
|
||||
* @return an {@link ByteBuffer}
|
||||
* @throws IOException
|
||||
* if any error occur while reading
|
||||
*/
|
||||
ByteBuffer read(Torrent torrent, Range torrentRange) throws IOException;
|
||||
|
||||
/**
|
||||
* Calculates the checksum of an given piece
|
||||
*
|
||||
* @param piece
|
||||
* the piece
|
||||
* @return the {@link Hash} for the requested piece
|
||||
* @throws IOException
|
||||
* if any error occur while reading data or calculating the
|
||||
* checksum
|
||||
*/
|
||||
Hash checksum(TorrentPiece piece) throws IOException;
|
||||
}
|
||||
39
src/main/java/com/torrent4j/storage/VoidTorrentStorage.java
Normal file
39
src/main/java/com/torrent4j/storage/VoidTorrentStorage.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package com.torrent4j.storage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
import com.torrent4j.util.Hash;
|
||||
import com.torrent4j.util.Range;
|
||||
|
||||
/**
|
||||
* This {@link TorrentStorage} implementation is similar to a UNIX
|
||||
* <code>/dev/null</code> for writing and <code>/dev/zero</code> for reading.
|
||||
* Data on this storage are never persisted and are discarded immediately. All
|
||||
* reads return a {@link ByteBuffer} composed entirely from <code>0</code>.
|
||||
* <p>
|
||||
* Also, in order for checksums to match, when checksumming an piece, the piece
|
||||
* hash itself is returned. This means that the checksums will <b>never</b>
|
||||
* fail, no matter what, independent from data contents.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class VoidTorrentStorage implements TorrentStorage {
|
||||
@Override
|
||||
public boolean write(Torrent torrent, Range range, ByteBuffer data)
|
||||
throws IOException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer read(Torrent torrent, Range range) throws IOException {
|
||||
return ByteBuffer.allocate((int) range.getLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hash checksum(TorrentPiece piece) {
|
||||
return piece.getHash();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.torrent4j.strategy;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
import com.torrent4j.model.TorrentPieceBlock;
|
||||
|
||||
/**
|
||||
* Determines the actions that should be taken regarding downloads
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public interface TorrentDownloadStrategy {
|
||||
/**
|
||||
* Notifies that an block has been received and stored in the storage.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param block
|
||||
* the block that has been downloaded
|
||||
* @param peer
|
||||
* the peer who uploaded the block
|
||||
*/
|
||||
void blockReceived(Torrent torrent, TorrentPieceBlock block,
|
||||
TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Notifies that an piece is complete and its checksum its corrects
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param piece
|
||||
* the completed piece
|
||||
* @param peer
|
||||
* the peer who uploaded the last block to complete this piece
|
||||
*/
|
||||
void pieceComplete(Torrent torrent, TorrentPiece piece, TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Notifies that an piece has completed but its checksum data is incorrect.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param piece
|
||||
* the piece which is complete but with wrong data
|
||||
* @param peer
|
||||
* the peer who uploaded the last block of the corrupted piece
|
||||
*/
|
||||
void pieceChecksumFailed(Torrent torrent, TorrentPiece piece,
|
||||
TorrentPeer peer);
|
||||
}
|
||||
136
src/main/java/com/torrent4j/strategy/TorrentPeerStrategy.java
Normal file
136
src/main/java/com/torrent4j/strategy/TorrentPeerStrategy.java
Normal file
@@ -0,0 +1,136 @@
|
||||
package com.torrent4j.strategy;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
|
||||
/**
|
||||
* Determine actions that the client should take regarding peers and their
|
||||
* requests.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public interface TorrentPeerStrategy {
|
||||
/**
|
||||
* Performs an action with a peer that has been recently discovered.
|
||||
* Discovery can happen through tracker announcements, DHT, peer exchange or
|
||||
* by manually adding them to the torrent object.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the discovered peer
|
||||
*/
|
||||
void peerDiscovered(Torrent torrent, TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Performs an action once handshaked with a peer.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the connected peer
|
||||
*/
|
||||
void peerConnected(Torrent torrent, TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Notifies that the bitfield for the given peer has been received.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the peer which sent his bitfield
|
||||
*/
|
||||
void bitField(Torrent torrent, TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Notifies that the peer has gotten a new piece
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the peer which downloaded a new piece
|
||||
* @param piece
|
||||
* the piece which the peer has received
|
||||
*/
|
||||
void havePiece(Torrent torrent, TorrentPeer peer, TorrentPiece piece);
|
||||
|
||||
/**
|
||||
* Notifies that the peer has unchoked the connection
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the peer
|
||||
*/
|
||||
void unchoked(Torrent torrent, TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Notifies that the peer has choked the connection
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the peer
|
||||
*/
|
||||
void choked(Torrent torrent, TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Notifies that the peer has interest in any of our pieces. Normally, when
|
||||
* this message is received, the remote peer is asking for an unchoke.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the peer that is interested in our pieces
|
||||
*/
|
||||
void interested(Torrent torrent, TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Notifies that the peer no more has interest in any of our pieces.
|
||||
* Normally, it is safe to choke the peer after this.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the peer that has no more intested in any of the pieces
|
||||
*/
|
||||
void notInterested(Torrent torrent, TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Notifies that the peer has been idle for some time. If required, an
|
||||
* <code>keepalive</code> message should be sent. It is also possible to
|
||||
* close the connection if the peer is no longer interesting.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the idle peer
|
||||
* @param idleTime
|
||||
* the peer's idle time in milliseconds
|
||||
*/
|
||||
void peerIdle(Torrent torrent, TorrentPeer peer, long idleTime);
|
||||
|
||||
/**
|
||||
* Notifies the the peer has been disconnected. Note that this method is
|
||||
* called either if the client closed the connection or if we requested an
|
||||
* disconnection.
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the disconnected peer
|
||||
*/
|
||||
void peerDisconnected(Torrent torrent, TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Notifies that the peer has been removed from the swarm list. Although it
|
||||
* is possible to re-add the peer to the list, this is not recommended!
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param peer
|
||||
* the removed peer
|
||||
*/
|
||||
void peerRemoved(Torrent torrent, TorrentPeer peer);
|
||||
}
|
||||
23
src/main/java/com/torrent4j/strategy/TorrentStrategy.java
Normal file
23
src/main/java/com/torrent4j/strategy/TorrentStrategy.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.torrent4j.strategy;
|
||||
|
||||
/**
|
||||
* Provides an easy method to access all torrent strategies
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public interface TorrentStrategy {
|
||||
/**
|
||||
* @return the download strategy
|
||||
*/
|
||||
TorrentDownloadStrategy getDownloadStrategy();
|
||||
|
||||
/**
|
||||
* @return the upload strategy
|
||||
*/
|
||||
TorrentUploadStrategy getUploadStrategy();
|
||||
|
||||
/**
|
||||
* @return the peer strategy
|
||||
*/
|
||||
TorrentPeerStrategy getPeerStrategy();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.torrent4j.strategy;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.model.TorrentPieceBlock;
|
||||
|
||||
/**
|
||||
* Determines the actions that should be taken regarding uploads
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public interface TorrentUploadStrategy {
|
||||
/**
|
||||
* Notifies that an given peer is requesting an block upload
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param block
|
||||
* the block requested for upload
|
||||
* @param peer
|
||||
* the peer requesting the block
|
||||
*/
|
||||
void blockRequested(Torrent torrent, TorrentPieceBlock block,
|
||||
TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Notifies that the peer has cancelled its previous request for the block
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param block
|
||||
* the previous requested block (now cancelled)
|
||||
* @param peer
|
||||
* the peer canceling the block request
|
||||
*/
|
||||
void blockRequestCancelled(Torrent torrent, TorrentPieceBlock block,
|
||||
TorrentPeer peer);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.torrent4j.strategy.standard;
|
||||
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
|
||||
/**
|
||||
* Selects an suitable piece for download
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public interface PieceSelector {
|
||||
/**
|
||||
* Applies an algorithm to determine the most suitable piece for download
|
||||
* from the peer. If no pieces are available or none are worth to download
|
||||
* from this peer, <code>null</code> should be returned.
|
||||
*
|
||||
* @param peer
|
||||
* the peer to download the piece from
|
||||
* @return the piece to start downloading, if any
|
||||
*/
|
||||
TorrentPiece selectPiece(TorrentPeer peer);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.torrent4j.strategy.standard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
|
||||
/**
|
||||
* Randomly selects an piece from the available peer pieces
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class RandomPieceSelector implements PieceSelector {
|
||||
@Override
|
||||
public TorrentPiece selectPiece(TorrentPeer peer) {
|
||||
final List<TorrentPiece> pieces = peer.getPieces().getMissingPieces(
|
||||
peer.getTorrent().getCompletedPieces());
|
||||
if (pieces.isEmpty())
|
||||
return null;
|
||||
return pieces.get((int) (Math.random() * pieces.size()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.torrent4j.strategy.standard;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
import com.torrent4j.model.TorrentPieceBlock;
|
||||
import com.torrent4j.model.TorrentSwarm.SwarmBroadcastHandler;
|
||||
import com.torrent4j.strategy.TorrentDownloadStrategy;
|
||||
|
||||
public class StandardTorrentDownloadStrategy implements TorrentDownloadStrategy {
|
||||
private final StandardTorrentStrategy strategy;
|
||||
|
||||
public StandardTorrentDownloadStrategy(StandardTorrentStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockReceived(Torrent torrent, TorrentPieceBlock block,
|
||||
TorrentPeer peer) {
|
||||
TorrentPieceBlock next = block.getNextBlock();
|
||||
if (next == null) {
|
||||
final TorrentPiece piece = strategy.getPieceSelector().selectPiece(
|
||||
peer);
|
||||
if (piece == null) {
|
||||
peer.withdrawInterest();
|
||||
return;
|
||||
}
|
||||
next = piece.getFirstBlock();
|
||||
}
|
||||
peer.requestBlock(next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pieceComplete(Torrent torrent,
|
||||
final TorrentPiece completePiece, TorrentPeer peer) {
|
||||
torrent.getSwarm().broadcast(new SwarmBroadcastHandler() {
|
||||
@Override
|
||||
public void broadcast(TorrentPeer peer) {
|
||||
peer.have(completePiece);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exception(Exception e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
final TorrentPiece piece = strategy.getPieceSelector()
|
||||
.selectPiece(peer);
|
||||
if (piece == null) {
|
||||
peer.withdrawInterest();
|
||||
return;
|
||||
}
|
||||
peer.requestBlock(piece.getFirstBlock());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pieceChecksumFailed(Torrent torrent, TorrentPiece piece,
|
||||
TorrentPeer peer) {
|
||||
System.out.println("Checksum has failed!");
|
||||
strategy.banPeer(peer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.torrent4j.strategy.standard;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.model.TorrentPiece;
|
||||
import com.torrent4j.strategy.TorrentPeerStrategy;
|
||||
|
||||
public class StandardTorrentPeerStrategy implements TorrentPeerStrategy {
|
||||
private final StandardTorrentStrategy strategy;
|
||||
|
||||
public StandardTorrentPeerStrategy(StandardTorrentStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peerDiscovered(Torrent torrent, TorrentPeer peer) {
|
||||
peer.connect();
|
||||
peer.handshake();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peerConnected(Torrent torrent, TorrentPeer peer) {
|
||||
peer.bitField();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bitField(Torrent torrent, TorrentPeer peer) {
|
||||
if (peer.getPieces().isSeeder()
|
||||
&& torrent.getCompletedPieces().isSeeder()) {
|
||||
peer.disconnect();
|
||||
return;
|
||||
}
|
||||
if (peer.getPieces().hasMissingPieces(torrent.getCompletedPieces())) {
|
||||
peer.declareInterest();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void havePiece(Torrent torrent, TorrentPeer peer, TorrentPiece piece) {
|
||||
if (torrent.getCompletedPieces().hasPiece(piece))
|
||||
return;
|
||||
if (peer.getState().hasDownloadRequestedBlock())
|
||||
return;
|
||||
if (peer.getState().isRemotellyChoked())
|
||||
return;
|
||||
peer.requestBlock(piece.getFirstBlock());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unchoked(Torrent torrent, TorrentPeer peer) {
|
||||
final TorrentPiece piece = strategy.getPieceSelector()
|
||||
.selectPiece(peer);
|
||||
if (piece == null) {
|
||||
peer.withdrawInterest();
|
||||
return;
|
||||
}
|
||||
peer.requestBlock(piece.getFirstBlock());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void choked(Torrent torrent, TorrentPeer peer) {
|
||||
if (peer.getPieces().isSeeder()
|
||||
&& torrent.getCompletedPieces().isSeeder()) {
|
||||
peer.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interested(Torrent torrent, TorrentPeer peer) {
|
||||
peer.unchoke();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notInterested(Torrent torrent, TorrentPeer peer) {
|
||||
peer.choke();
|
||||
if (peer.getPieces().isSeeder()
|
||||
&& torrent.getCompletedPieces().isSeeder()) {
|
||||
peer.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peerIdle(Torrent torrent, TorrentPeer peer, long idleTime) {
|
||||
peer.keepAlive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peerDisconnected(Torrent torrent, TorrentPeer peer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peerRemoved(Torrent torrent, TorrentPeer peer) {
|
||||
if (peer.isConnected())
|
||||
peer.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.torrent4j.strategy.standard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.strategy.TorrentDownloadStrategy;
|
||||
import com.torrent4j.strategy.TorrentPeerStrategy;
|
||||
import com.torrent4j.strategy.TorrentStrategy;
|
||||
import com.torrent4j.strategy.TorrentUploadStrategy;
|
||||
|
||||
public class StandardTorrentStrategy implements TorrentStrategy {
|
||||
private final TorrentDownloadStrategy downloadStrategy = new StandardTorrentDownloadStrategy(
|
||||
this);
|
||||
private final TorrentUploadStrategy uploadStrategy = new StandardTorrentUploadStrategy(
|
||||
this);
|
||||
private final TorrentPeerStrategy peerStrategy = new StandardTorrentPeerStrategy(
|
||||
this);
|
||||
private final PieceSelector pieceSelector;
|
||||
|
||||
private final List<TorrentPeer> bannedPeers = new ArrayList<>();
|
||||
|
||||
public StandardTorrentStrategy(PieceSelector pieceSelector) {
|
||||
this.pieceSelector = pieceSelector;
|
||||
}
|
||||
|
||||
public StandardTorrentStrategy() {
|
||||
this.pieceSelector = new RandomPieceSelector();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentDownloadStrategy getDownloadStrategy() {
|
||||
return downloadStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentUploadStrategy getUploadStrategy() {
|
||||
return uploadStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentPeerStrategy getPeerStrategy() {
|
||||
return peerStrategy;
|
||||
}
|
||||
|
||||
public PieceSelector getPieceSelector() {
|
||||
return pieceSelector;
|
||||
}
|
||||
|
||||
public void banPeer(TorrentPeer peer) {
|
||||
bannedPeers.add(peer);
|
||||
}
|
||||
|
||||
public void unbanPeer(TorrentPeer peer) {
|
||||
bannedPeers.remove(peer);
|
||||
}
|
||||
|
||||
public boolean isBanned(TorrentPeer peer) {
|
||||
return bannedPeers.contains(peer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.torrent4j.strategy.standard;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.torrent4j.model.Torrent;
|
||||
import com.torrent4j.model.TorrentPeer;
|
||||
import com.torrent4j.model.TorrentPieceBlock;
|
||||
import com.torrent4j.storage.TorrentStorage;
|
||||
import com.torrent4j.strategy.TorrentUploadStrategy;
|
||||
|
||||
public class StandardTorrentUploadStrategy implements TorrentUploadStrategy {
|
||||
@SuppressWarnings("unused")
|
||||
private final StandardTorrentStrategy strategy;
|
||||
|
||||
public StandardTorrentUploadStrategy(StandardTorrentStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockRequested(Torrent torrent, TorrentPieceBlock block,
|
||||
TorrentPeer peer) {
|
||||
final TorrentStorage storage = torrent.getController().getStorage();
|
||||
try {
|
||||
peer.sendBlock(block,
|
||||
storage.read(torrent, block.getTorrentRange()));
|
||||
} catch (IOException e) {
|
||||
peer.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockRequestCancelled(Torrent torrent, TorrentPieceBlock block,
|
||||
TorrentPeer peer) {
|
||||
// we don't queue uploads, so we don't need to worry about this
|
||||
}
|
||||
}
|
||||
102
src/main/java/com/torrent4j/util/BitField.java
Normal file
102
src/main/java/com/torrent4j/util/BitField.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package com.torrent4j.util;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitField extends BitSet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int length;
|
||||
|
||||
public BitField(int length) {
|
||||
super(length);
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
private void ensureBitFieldLength(int index) {
|
||||
if (index > length)
|
||||
throw new ArrayIndexOutOfBoundsException(index + " is bigger than "
|
||||
+ length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flip(int bitIndex) {
|
||||
ensureBitFieldLength(bitIndex);
|
||||
super.flip(bitIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flip(int fromIndex, int toIndex) {
|
||||
ensureBitFieldLength(fromIndex);
|
||||
ensureBitFieldLength(toIndex);
|
||||
super.flip(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int bitIndex) {
|
||||
ensureBitFieldLength(bitIndex);
|
||||
super.set(bitIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int bitIndex, boolean value) {
|
||||
ensureBitFieldLength(bitIndex);
|
||||
super.set(bitIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int fromIndex, int toIndex) {
|
||||
ensureBitFieldLength(fromIndex);
|
||||
ensureBitFieldLength(toIndex);
|
||||
super.set(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int fromIndex, int toIndex, boolean value) {
|
||||
ensureBitFieldLength(fromIndex);
|
||||
ensureBitFieldLength(toIndex);
|
||||
super.set(fromIndex, toIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(int bitIndex) {
|
||||
ensureBitFieldLength(bitIndex);
|
||||
super.clear(bitIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(int fromIndex, int toIndex) {
|
||||
ensureBitFieldLength(fromIndex);
|
||||
ensureBitFieldLength(toIndex);
|
||||
super.clear(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean get(int bitIndex) {
|
||||
ensureBitFieldLength(bitIndex);
|
||||
return super.get(bitIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitSet get(int fromIndex, int toIndex) {
|
||||
ensureBitFieldLength(fromIndex);
|
||||
ensureBitFieldLength(toIndex);
|
||||
return super.get(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextSetBit(int fromIndex) {
|
||||
ensureBitFieldLength(fromIndex);
|
||||
return super.nextSetBit(fromIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextClearBit(int fromIndex) {
|
||||
ensureBitFieldLength(fromIndex);
|
||||
return super.nextClearBit(fromIndex);
|
||||
}
|
||||
}
|
||||
55
src/main/java/com/torrent4j/util/Hash.java
Normal file
55
src/main/java/com/torrent4j/util/Hash.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package com.torrent4j.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Hash {
|
||||
private final HashType type;
|
||||
private final byte[] hash;
|
||||
|
||||
public Hash(HashType type, byte[] hash) {
|
||||
this.type = type;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
public HashType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return type.toString(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(hash);
|
||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (!(obj instanceof Hash))
|
||||
return false;
|
||||
Hash other = (Hash) obj;
|
||||
if (!Arrays.equals(hash, other.hash))
|
||||
return false;
|
||||
if (type != other.type)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type.name() + ":" + getString();
|
||||
}
|
||||
}
|
||||
105
src/main/java/com/torrent4j/util/HashType.java
Normal file
105
src/main/java/com/torrent4j/util/HashType.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package com.torrent4j.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
public enum HashType {
|
||||
SHA1(new SimpleHashDelegate("SHA1")),
|
||||
|
||||
SHA256(new SimpleHashDelegate("SHA-256")),
|
||||
|
||||
SHA384(new SimpleHashDelegate("SHA-384")),
|
||||
|
||||
SHA512(new SimpleHashDelegate("SHA-512")),
|
||||
|
||||
MD5(new SimpleHashDelegate("MD5")),
|
||||
|
||||
MD2(new SimpleHashDelegate("MD2"));
|
||||
|
||||
private final HashDelegate delegate;
|
||||
|
||||
HashType(HashDelegate delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public byte[] hash(byte[] data) {
|
||||
return delegate.hash(data);
|
||||
}
|
||||
|
||||
public byte[] hash(String string) {
|
||||
return hash(string.getBytes());
|
||||
}
|
||||
|
||||
public byte[] hash(ByteBuffer buffer) {
|
||||
final byte[] data = new byte[buffer.remaining()];
|
||||
buffer.get(data);
|
||||
return hash(data);
|
||||
}
|
||||
|
||||
public String hashAsString(byte[] data) {
|
||||
return toString(hash(data));
|
||||
}
|
||||
|
||||
public String hashAsString(String string) {
|
||||
return hashAsString(string.getBytes());
|
||||
}
|
||||
|
||||
public String hashAsString(ByteBuffer buffer) {
|
||||
final byte[] data = new byte[buffer.remaining()];
|
||||
buffer.get(data);
|
||||
return hashAsString(data);
|
||||
}
|
||||
|
||||
public String toString(byte[] hash) {
|
||||
return delegate.toString(hash);
|
||||
}
|
||||
|
||||
public byte[] fromString(String hash) {
|
||||
return delegate.fromString(hash);
|
||||
}
|
||||
|
||||
private interface HashDelegate {
|
||||
byte[] hash(byte[] data);
|
||||
|
||||
String toString(byte[] hash);
|
||||
|
||||
byte[] fromString(String hash);
|
||||
}
|
||||
|
||||
private static class SimpleHashDelegate implements HashDelegate {
|
||||
private final String name;
|
||||
|
||||
protected SimpleHashDelegate(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] hash(byte[] data) {
|
||||
try {
|
||||
final MessageDigest hasher = MessageDigest.getInstance(name);
|
||||
hasher.update(data);
|
||||
return hasher.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(byte[] hash) {
|
||||
return new String(Hex.encodeHex(hash));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fromString(String hash) {
|
||||
try {
|
||||
return Hex.decodeHex(hash.toCharArray());
|
||||
} catch (DecoderException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/main/java/com/torrent4j/util/PeerIDGenerator.java
Normal file
11
src/main/java/com/torrent4j/util/PeerIDGenerator.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.torrent4j.util;
|
||||
|
||||
public class PeerIDGenerator {
|
||||
public static String generateRandomPeerID() {
|
||||
byte[] id = new byte[20];
|
||||
for (int i = 0; i < id.length; i++) {
|
||||
id[i] = (byte) (Math.random() * Byte.MAX_VALUE);
|
||||
}
|
||||
return new String(id);
|
||||
}
|
||||
}
|
||||
178
src/main/java/com/torrent4j/util/Range.java
Normal file
178
src/main/java/com/torrent4j/util/Range.java
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* 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 com.torrent4j.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This class represents an interval.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public final class Range implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final long start, length;
|
||||
|
||||
/**
|
||||
* Creates a range with the given parameters
|
||||
*
|
||||
* @param start
|
||||
* the beginning of the range
|
||||
* @param length
|
||||
* the length of the range
|
||||
* @return a range
|
||||
*/
|
||||
public static Range getRangeByLength(long start, long length) {
|
||||
return new Range(start, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the smallest possible range containing the given numbers.
|
||||
*
|
||||
* @param number1
|
||||
* number to be within the range
|
||||
* @param number2
|
||||
* number to be within the range
|
||||
* @return
|
||||
*/
|
||||
public static Range getRangeByNumbers(long number1, long number2) {
|
||||
long s = Math.min(number1, number2);
|
||||
return new Range(s, Math.max(number1, number2) - s + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new range
|
||||
*
|
||||
* @param start
|
||||
* @param length
|
||||
*/
|
||||
private Range(long start, long length) {
|
||||
if (start < 0)
|
||||
throw new IllegalArgumentException("start must be >= 0");
|
||||
if (length < 0)
|
||||
throw new IllegalArgumentException("length must be >= 0");
|
||||
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param range
|
||||
* @return true if the given range is contained within this range
|
||||
*/
|
||||
public boolean contains(Range range) {
|
||||
return getStart() <= range.getStart() && getEnd() >= range.getEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pos
|
||||
* @return true if the given point is contained within this range
|
||||
*/
|
||||
public boolean contains(long pos) {
|
||||
return getStart() <= pos && getEnd() >= pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Range) {
|
||||
Range r = (Range) obj;
|
||||
return getStart() == r.getStart() && getLength() == r.getLength();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last index contained in this range
|
||||
*/
|
||||
public long getEnd() {
|
||||
return start + length - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of this range
|
||||
*/
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first index contained in this range
|
||||
*/
|
||||
public long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if this range defines a zero-sized range.
|
||||
*
|
||||
* @return true if zero-sized.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return length == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) ((getStart() * 13) & (getEnd() * 137));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a range which contains only the indices contained in the
|
||||
* intersection of this range and the given range.
|
||||
*
|
||||
* @param range
|
||||
* the range to intersect with
|
||||
* @return the intersected range or null if the ranges don't overlap
|
||||
*/
|
||||
public Range intersection(Range range) {
|
||||
if (!intersects(range)) {
|
||||
return null;
|
||||
}
|
||||
return getRangeByNumbers(Math.max(getStart(), range.getStart()),
|
||||
Math.min(getEnd(), range.getEnd()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of indices which are in this range and the given
|
||||
* range.
|
||||
*
|
||||
* @param r
|
||||
* @return 0 if the ranges don't overlap, the length of the intersection
|
||||
* between them otherwise
|
||||
*/
|
||||
public long intersectionLength(Range r) {
|
||||
if (!intersects(r)) {
|
||||
return 0;
|
||||
}
|
||||
return Math.min(getEnd(), r.getEnd())
|
||||
- Math.max(getStart(), r.getStart()) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param range
|
||||
* the range to intersect test with
|
||||
* @return true if the ranges overlap
|
||||
*/
|
||||
public boolean intersects(Range range) {
|
||||
return getStart() <= range.getEnd() && getEnd() >= range.getStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + getStart() + " - " + getEnd() + "]";
|
||||
}
|
||||
}
|
||||
325
src/main/java/com/torrent4j/util/bencoding/BDecoder.java
Normal file
325
src/main/java/com/torrent4j/util/bencoding/BDecoder.java
Normal file
@@ -0,0 +1,325 @@
|
||||
package com.torrent4j.util.bencoding;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Helper class to decode "bencoded" data.
|
||||
*
|
||||
* @author Dennis "Bytekeeper" Waldherr
|
||||
*
|
||||
*/
|
||||
public class BDecoder extends InputStream {
|
||||
private static final int MAX_STRING_LENGTH = 1024 * 1024;
|
||||
|
||||
private final PushbackInputStream in;
|
||||
|
||||
public BDecoder(InputStream in) {
|
||||
this.in = new PushbackInputStream(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to decode exactly one bencoded element.
|
||||
*
|
||||
* @param bencode
|
||||
* @return
|
||||
* @throws BDecodingException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Object bdecode(byte[] bencode) throws BDecodingException {
|
||||
BDecoder in = new BDecoder(new ByteArrayInputStream(bencode));
|
||||
try {
|
||||
return in.readElement();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one bencoded element.
|
||||
*
|
||||
* @param in
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws BDecodingException
|
||||
*/
|
||||
public Object readElement() throws IOException, BDecodingException {
|
||||
return bdecode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string result from bencoding to a java String. If the data is
|
||||
* already a String or null it is simply returned.
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* @throws BTypeException
|
||||
*/
|
||||
private static String stringOf(Object data) throws BTypeException {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (data instanceof byte[]) {
|
||||
return new String((byte[]) data, "UTF-8");
|
||||
} else if (data instanceof String) {
|
||||
return (String) data;
|
||||
} else {
|
||||
throw new BTypeException("Unsupported type: " + data.getClass());
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a constructed or decoded integer to a java Integer. If the data
|
||||
* is already an Integer or null it is simply returned.
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* @throws BTypeException
|
||||
*/
|
||||
private static Integer intOf(Object data) throws BTypeException {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
if (data instanceof BigInteger) {
|
||||
return ((BigInteger) data).intValue();
|
||||
} else if (data instanceof Integer) {
|
||||
return (Integer) data;
|
||||
} else {
|
||||
throw new BTypeException("Unsupported type: " + data.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a constructed or decoded integer to a java Integer. If the data
|
||||
* is already an Integer or null it is simply returned.
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* @throws BTypeException
|
||||
*/
|
||||
private static Long longOf(Object data) throws BTypeException {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
if (data instanceof BigInteger) {
|
||||
return ((BigInteger) data).longValue();
|
||||
} else if (data instanceof Integer) {
|
||||
return Long.valueOf((Integer) data);
|
||||
} else if (data instanceof Long) {
|
||||
return (Long) data;
|
||||
} else {
|
||||
throw new BTypeException("Unsupported type: " + data.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private Object bdecode() throws IOException, BDecodingException {
|
||||
int head = in.read();
|
||||
switch (head) {
|
||||
case -1:
|
||||
throw new EOFException();
|
||||
case 'l':
|
||||
return bdecodeList();
|
||||
case 'i':
|
||||
return bdecodeInteger();
|
||||
case 'd':
|
||||
return bdecodeDictionary();
|
||||
default:
|
||||
if (Character.isDigit(head)) {
|
||||
in.unread(head);
|
||||
return bdecodeString();
|
||||
}
|
||||
}
|
||||
throw new BDecodingException("Parameter is not bencoded data.");
|
||||
}
|
||||
|
||||
private BMap bdecodeDictionary() throws IOException, BDecodingException {
|
||||
assert in != null;
|
||||
|
||||
BMap map = new BMapImpl();
|
||||
int head;
|
||||
while ((head = in.read()) != 'e') {
|
||||
if (head < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
in.unread(head);
|
||||
String key;
|
||||
try {
|
||||
key = stringOf(bdecodeString());
|
||||
} catch (BTypeException e) {
|
||||
throw new BDecodingException(e);
|
||||
}
|
||||
map.put(key, bdecode());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private byte[] bdecodeString() throws IOException, BDecodingException {
|
||||
assert in != null;
|
||||
int len = 0;
|
||||
int head;
|
||||
while ((head = in.read()) != ':') {
|
||||
if (head < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
len = len * 10 + (head - '0');
|
||||
if (len > MAX_STRING_LENGTH) {
|
||||
throw new BDecodingException("Encoded string length exceeds "
|
||||
+ MAX_STRING_LENGTH + " bytes!");
|
||||
}
|
||||
}
|
||||
byte data[] = new byte[len];
|
||||
int off = 0;
|
||||
while (len > 0) {
|
||||
int read = in.read(data, off, len);
|
||||
len -= read;
|
||||
off += read;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private Object bdecodeInteger() throws IOException, BDecodingException {
|
||||
assert in != null;
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
int head;
|
||||
while ((head = in.read()) != 'e') {
|
||||
if (head < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
if (!Character.isDigit(head)) {
|
||||
throw new BDecodingException("Expected digit but got: " + head);
|
||||
}
|
||||
b.append((char) head);
|
||||
}
|
||||
return new BigInteger(b.toString());
|
||||
}
|
||||
|
||||
private BList bdecodeList() throws IOException, BDecodingException {
|
||||
assert in != null;
|
||||
|
||||
BList list = new BListImpl();
|
||||
int head;
|
||||
while ((head = in.read()) != 'e') {
|
||||
if (head < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
in.unread(head);
|
||||
list.add(bdecode());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return in.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return in.read(b, off, len);
|
||||
}
|
||||
|
||||
private static class BListImpl extends ArrayList<Object> implements BList {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Integer getInteger(int index) throws BTypeException {
|
||||
return intOf(get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLong(int index) throws BTypeException {
|
||||
return longOf(get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int index) throws BTypeException {
|
||||
return stringOf(get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BList getList(int index) throws BTypeException {
|
||||
Object tmp = get(index);
|
||||
if (tmp instanceof BList) {
|
||||
return (BList) tmp;
|
||||
}
|
||||
throw new BTypeException("Unexpected type: " + tmp.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BMap getMap(int index) throws BTypeException {
|
||||
Object tmp = get(index);
|
||||
if (tmp instanceof BMap) {
|
||||
return (BMap) tmp;
|
||||
}
|
||||
throw new BTypeException("Unexpected type: " + tmp.getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class BMapImpl extends HashMap<String, Object> implements
|
||||
BMap {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Integer getInteger(String key) throws BTypeException {
|
||||
return intOf(get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLong(String key) throws BTypeException {
|
||||
return longOf(get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key) throws BTypeException {
|
||||
return stringOf(get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BList getList(String key) throws BTypeException {
|
||||
Object tmp = get(key);
|
||||
if (tmp instanceof BList) {
|
||||
return (BList) tmp;
|
||||
}
|
||||
if (tmp != null)
|
||||
throw new BTypeException("Unexpected type: " + tmp.getClass());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BMap getMap(String key) throws BTypeException {
|
||||
Object tmp = get(key);
|
||||
if (tmp instanceof BMap) {
|
||||
return (BMap) tmp;
|
||||
}
|
||||
throw new BTypeException("Unexpected type: " + tmp.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
public static final Object decode(String encoded) throws IOException {
|
||||
try(final ByteArrayInputStream in = new ByteArrayInputStream(
|
||||
encoded.getBytes())) {
|
||||
return new BDecoder(in).readElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.torrent4j.util.bencoding;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* BDecodingException: an error has occured when decoding bencoded data.
|
||||
*
|
||||
* @author Dennis "Bytekeeper" Waldherr
|
||||
*/
|
||||
public class BDecodingException extends IOException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BDecodingException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public BDecodingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
180
src/main/java/com/torrent4j/util/bencoding/BEncoder.java
Normal file
180
src/main/java/com/torrent4j/util/bencoding/BEncoder.java
Normal file
@@ -0,0 +1,180 @@
|
||||
package com.torrent4j.util.bencoding;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class to "BEncode" data.
|
||||
*
|
||||
* @author Dennis "Bytekeeper" Waldherr
|
||||
*
|
||||
*/
|
||||
public final class BEncoder extends OutputStream {
|
||||
static final Comparator<String> BYTE_COMPARATOR = new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
byte b1[] = bytesOf(o1);
|
||||
byte b2[] = bytesOf(o2);
|
||||
int len = Math.min(b1.length, b2.length);
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (b1[i] > b2[i]) {
|
||||
return 1;
|
||||
}
|
||||
if (b1[i] < b2[i]) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return b1.length - b2.length;
|
||||
}
|
||||
};
|
||||
|
||||
private final OutputStream out;
|
||||
|
||||
/**
|
||||
* Converts a string into the raw byte array representation used to store
|
||||
* into bencoded data.
|
||||
*
|
||||
* @param s
|
||||
* @return
|
||||
*/
|
||||
public static byte[] bytesOf(String s) {
|
||||
try {
|
||||
return s.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private BEncoder(OutputStream out) {
|
||||
if (out == null)
|
||||
throw new InvalidParameterException("output stream is null");
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
public static byte[] bencode(Object obj) {
|
||||
if (obj == null)
|
||||
throw new InvalidParameterException("Object to encode is null!");
|
||||
try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
try (final BEncoder bout = new BEncoder(out)) {
|
||||
bout.writeElement(obj);
|
||||
}
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BEncodes data and outputs it to an OutputStream.
|
||||
*
|
||||
* @param out
|
||||
* @param obj
|
||||
* @throws IOException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void writeElement(Object obj) throws IOException {
|
||||
if (obj == null)
|
||||
throw new InvalidParameterException("Object to encode is null!");
|
||||
|
||||
if (obj instanceof String) {
|
||||
bencodeString((String) obj);
|
||||
} else if (obj instanceof byte[]) {
|
||||
bencodeString((byte[]) obj);
|
||||
} else if (obj instanceof Integer) {
|
||||
bencodeInteger(BigInteger.valueOf((Integer) obj));
|
||||
} else if (obj instanceof Long) {
|
||||
bencodeInteger(BigInteger.valueOf((Long) obj));
|
||||
} else if (obj instanceof BigInteger) {
|
||||
bencodeInteger((BigInteger) obj);
|
||||
} else if (obj instanceof Collection) {
|
||||
bencodeList((Collection<?>) obj);
|
||||
} else if (obj instanceof Map) {
|
||||
bencodeDictionary((Map<String, ?>) obj);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Type " + obj.getClass()
|
||||
+ " is not bencodable.");
|
||||
}
|
||||
}
|
||||
|
||||
private void bencodeDictionary(Map<String, ?> dict) throws IOException {
|
||||
assert out != null;
|
||||
assert dict != null;
|
||||
|
||||
out.write('d');
|
||||
List<String> sorted = new ArrayList<String>(dict.keySet());
|
||||
Collections.sort(sorted, BYTE_COMPARATOR);
|
||||
for (String key : sorted) {
|
||||
writeElement(key);
|
||||
writeElement(dict.get(key));
|
||||
}
|
||||
out.write('e');
|
||||
}
|
||||
|
||||
private void bencodeList(Collection<?> list) throws IOException {
|
||||
assert out != null;
|
||||
assert list != null;
|
||||
out.write('l');
|
||||
for (Object child : list) {
|
||||
writeElement(child);
|
||||
}
|
||||
out.write('e');
|
||||
}
|
||||
|
||||
private void bencodeInteger(BigInteger integer) throws IOException {
|
||||
assert integer != null;
|
||||
out.write('i');
|
||||
out.write(bytesOf(integer.toString()));
|
||||
out.write('e');
|
||||
}
|
||||
|
||||
private void bencodeString(String string) throws IOException {
|
||||
assert string != null;
|
||||
byte[] bytes = bytesOf(string);
|
||||
bencodeString(bytes);
|
||||
}
|
||||
|
||||
private void bencodeString(byte[] data) throws IOException {
|
||||
assert data != null;
|
||||
out.write(bytesOf(Integer.toString(data.length)));
|
||||
out.write(':');
|
||||
out.write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int arg0) throws IOException {
|
||||
out.write(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
out.write(b, off, len);
|
||||
}
|
||||
|
||||
public static final String encode(Object object) throws IOException {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
|
||||
new BEncoder(out).writeElement(object);
|
||||
return new String(out.toByteArray());
|
||||
}
|
||||
|
||||
public static final byte[] encodeToByteArray(Object object)
|
||||
throws IOException {
|
||||
try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
new BEncoder(out).writeElement(object);
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/main/java/com/torrent4j/util/bencoding/BList.java
Normal file
21
src/main/java/com/torrent4j/util/bencoding/BList.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.torrent4j.util.bencoding;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Representation of a bencoded list.
|
||||
*
|
||||
* @author Bytekeeper
|
||||
*
|
||||
*/
|
||||
public interface BList extends List<Object> {
|
||||
String getString(int index) throws BTypeException;
|
||||
|
||||
Integer getInteger(int index) throws BTypeException;
|
||||
|
||||
Long getLong(int index) throws BTypeException;
|
||||
|
||||
BList getList(int index) throws BTypeException;
|
||||
|
||||
BMap getMap(int index) throws BTypeException;
|
||||
}
|
||||
21
src/main/java/com/torrent4j/util/bencoding/BMap.java
Normal file
21
src/main/java/com/torrent4j/util/bencoding/BMap.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.torrent4j.util.bencoding;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Representation of a bencoded map.
|
||||
*
|
||||
* @author Bytekeeper
|
||||
*
|
||||
*/
|
||||
public interface BMap extends Map<String, Object> {
|
||||
String getString(String key) throws BTypeException;
|
||||
|
||||
Integer getInteger(String key) throws BTypeException;
|
||||
|
||||
Long getLong(String key) throws BTypeException;
|
||||
|
||||
BList getList(String key) throws BTypeException;
|
||||
|
||||
BMap getMap(String key) throws BTypeException;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.torrent4j.util.bencoding;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class BTypeException extends IOException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BTypeException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
32
src/main/java/com/torrent4j/util/bencoding/HashBMap.java
Normal file
32
src/main/java/com/torrent4j/util/bencoding/HashBMap.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package com.torrent4j.util.bencoding;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class HashBMap extends HashMap<String, Object> implements BMap {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Integer getInteger(String key) {
|
||||
return (Integer) get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BList getList(String key) {
|
||||
return (BList) get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLong(String key) {
|
||||
return (Long) get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BMap getMap(String key) {
|
||||
return (BMap) get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key) {
|
||||
return (String) get(key);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user