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