diff --git a/README.md b/README.md
index e014586bb..fd1028b48 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,12 @@ Client client = new Client(
new File("/path/to/your.torrent"),
new File("/path/to/output/directory")));
+// You can optionally set download/upload rate limits
+// in kB/second. Setting a limit to 0.0 disables rate
+// limits.
+client.setMaxDownloadRate(50.0);
+client.setMaxUploadRate(50.0);
+
// At this point, can you either call download() to download the torrent and
// stop immediately after...
client.download();
@@ -134,7 +140,7 @@ License version 2.0. See COPYING file for more details.
Authors and contributors
------------------------
-* Maxime Petazzoni <> (Platform Engineer at Turn, Inc)
+* Maxime Petazzoni <> (Software Engineer at SignalFuse, Inc)
Original author, main developer and maintainer
* David Giffin <>
Contributed parallel hashing and multi-file torrent support.
diff --git a/bin/tracker b/bin/tracker
deleted file mode 100755
index 95aca596b..000000000
--- a/bin/tracker
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2012 Turn, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-base=$(dirname $(readlink -f $0))
-exec java -cp $(find ${base}/../build -name "ttorrent-*.jar" | tail -n 1) com.turn.ttorrent.tracker.Tracker $*
diff --git a/bin/ttorrent b/bin/ttorrent
new file mode 100755
index 000000000..fc21deaf6
--- /dev/null
+++ b/bin/ttorrent
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+# Copyright (C) 2012 Turn, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+EXECJAR="ttorrent-*-shaded.jar"
+
+real_path() {
+ case $1 in
+ /*)
+ SCRIPT="$1"
+ ;;
+ *)
+ PWD=`pwd`
+ SCRIPT="$PWD/$1"
+ ;;
+ esac
+ CHANGED=true
+ while [ "X$CHANGED" != "X" ] ; do
+ # Change spaces to ":" so the tokens can be parsed.
+ SAFESCRIPT=`echo $SCRIPT | sed -e 's; ;:;g'`
+ # Get the real path to this script, resolving any symbolic links
+ TOKENS=`echo $SAFESCRIPT | sed -e 's;/; ;g'`
+ REALPATH=
+ for C in $TOKENS; do
+ # Change any ":" in the token back to a space.
+ C=`echo $C | sed -e 's;:; ;g'`
+ REALPATH="$REALPATH/$C"
+ # If REALPATH is a sym link, resolve it. Loop for nested links.
+ while [ -h "$REALPATH" ] ; do
+ LS="`ls -ld "$REALPATH"`"
+ LINK="`expr "$LS" : '.*-> \(.*\)$'`"
+ if expr "$LINK" : '/.*' > /dev/null; then
+ # LINK is absolute.
+ REALPATH="$LINK"
+ else
+ # LINK is relative.
+ REALPATH="`dirname "$REALPATH"`""/$LINK"
+ fi
+ done
+ done
+ if [ "$REALPATH" = "$SCRIPT" ] ; then
+ CHANGED=""
+ else
+ SCRIPT="$REALPATH"
+ fi
+ done
+ echo "$REALPATH"
+}
+
+base=$(dirname $(real_path $0))
+CPARG=$(find ${base}/../build -name "$EXECJAR" | tail -n 1)
+if [ -z "$CPARG" ] ; then
+ echo "Unable to find $EXECJAR"
+ exit 1
+fi
+if [ -z "$MAINCLASS" ] ; then
+ CPARG="-jar $CPARG"
+else
+ CPARG="-cp $CPARG $MAINCLASS"
+fi
+exec java $CPARG "$@"
diff --git a/bin/client b/bin/ttorrent-torrent
similarity index 82%
rename from bin/client
rename to bin/ttorrent-torrent
index cd77b761a..4193c4e2c 100755
--- a/bin/client
+++ b/bin/ttorrent-torrent
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
# Copyright (C) 2012 Turn, Inc.
#
@@ -14,5 +14,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-base=$(dirname $(readlink -f $0))
-exec java -jar $(find ${base}/../build -name "ttorrent-*.jar" | tail -n 1) $*
+EXEFILE="${0%-torrent}"
+MAINCLASS="com.turn.ttorrent.cli.TorrentMain" "${EXEFILE}" "$@"
diff --git a/bin/torrent b/bin/ttorrent-tracker
similarity index 78%
rename from bin/torrent
rename to bin/ttorrent-tracker
index 99d46d258..e6bacd5d9 100755
--- a/bin/torrent
+++ b/bin/ttorrent-tracker
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
# Copyright (C) 2012 Turn, Inc.
#
@@ -14,5 +14,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-base=$(dirname $(readlink -f $0))
-exec java -cp $(find ${base}/../build -name "ttorrent-*.jar" | tail -n 1) com.turn.ttorrent.common.Torrent $*
+EXEFILE="${0%-tracker}"
+MAINCLASS="com.turn.ttorrent.cli.TrackerMain" "${EXEFILE}" "$@"
diff --git a/pom.xml b/pom.xml
index 33d6c98ca..389d5b930 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,6 +6,7 @@
org.sonatype.oss
oss-parent
7
+
Java BitTorrent library
@@ -17,7 +18,7 @@
http://turn.github.com/ttorrent/
com.turn
ttorrent
- 1.2
+ 1.3-SNAPSHOT
jar
@@ -27,7 +28,9 @@
scm:git:git://github.com/turn/ttorrent.git
+ scm:git:ssh://git@github.com/turn/ttorrent.git
http://github.com/turn/ttorrent
+ HEAD
@@ -46,10 +49,10 @@
mpetazzoni
Maxime Petazzoni
- mpetazzoni@turn.com
+ maxime.petazzoni@bulix.org
http://www.bulix.org
- Turn, Inc
- http://www.turn.com
+ SignalFuse, Inc
+ http://www.signalfuse.com
maintainer
architect
@@ -75,10 +78,16 @@
+
+ commons-codec
+ commons-codec
+ 1.8
+
+
commons-io
commons-io
- 2.1
+ 2.4
@@ -127,6 +136,11 @@
maven-jar-plugin
2.4
+
+
+ true
+
+
**
@@ -144,28 +158,24 @@
- maven-assembly-plugin
-
-
- jar-with-dependencies
-
- false
-
-
- false
- true
- com.turn.ttorrent.client.Client
-
-
-
+ maven-shade-plugin
+ 2.1
- make-my-jar-with-dependencies
package
-
- assembly
+ shade
+
+ ${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar
+
+
+
+ com.turn.ttorrent.cli.ClientMain
+
+
+
+
diff --git a/src/main/java/com/turn/ttorrent/bcodec/BDecoder.java b/src/main/java/com/turn/ttorrent/bcodec/BDecoder.java
index 305c56170..29a941dc9 100644
--- a/src/main/java/com/turn/ttorrent/bcodec/BDecoder.java
+++ b/src/main/java/com/turn/ttorrent/bcodec/BDecoder.java
@@ -206,7 +206,7 @@ public BEValue bdecodeNumber() throws IOException {
c = this.read();
if (c == '0')
throw new InvalidBEncodingException("Negative zero not allowed");
- chars[off] = (char)c;
+ chars[off] = '-';
off++;
}
diff --git a/src/main/java/com/turn/ttorrent/cli/ClientMain.java b/src/main/java/com/turn/ttorrent/cli/ClientMain.java
new file mode 100644
index 000000000..ae56e5f75
--- /dev/null
+++ b/src/main/java/com/turn/ttorrent/cli/ClientMain.java
@@ -0,0 +1,176 @@
+/**
+ * Copyright (C) 2011-2013 Turn, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.turn.ttorrent.cli;
+
+import com.turn.ttorrent.client.Client;
+import com.turn.ttorrent.client.SharedTorrent;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.channels.UnsupportedAddressTypeException;
+import java.util.Enumeration;
+
+import jargs.gnu.CmdLineParser;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.PatternLayout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Command-line entry-point for starting a {@link Client}
+ */
+public class ClientMain {
+
+ private static final Logger logger =
+ LoggerFactory.getLogger(ClientMain.class);
+
+ /**
+ * Default data output directory.
+ */
+ private static final String DEFAULT_OUTPUT_DIRECTORY = "/tmp";
+
+ /**
+ * Returns a usable {@link Inet4Address} for the given interface name.
+ *
+ *
+ * If an interface name is given, return the first usable IPv4 address for
+ * that interface. If no interface name is given or if that interface
+ * doesn't have an IPv4 address, return's localhost address (if IPv4).
+ *
+ *
+ *
+ * It is understood this makes the client IPv4 only, but it is important to
+ * remember that most BitTorrent extensions (like compact peer lists from
+ * trackers and UDP tracker support) are IPv4-only anyway.
+ *
+ *
+ * @param iface The network interface name.
+ * @return A usable IPv4 address as a {@link Inet4Address}.
+ * @throws UnsupportedAddressTypeException If no IPv4 address was available
+ * to bind on.
+ */
+ private static Inet4Address getIPv4Address(String iface)
+ throws SocketException, UnsupportedAddressTypeException,
+ UnknownHostException {
+ if (iface != null) {
+ Enumeration addresses =
+ NetworkInterface.getByName(iface).getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ InetAddress addr = addresses.nextElement();
+ if (addr instanceof Inet4Address) {
+ return (Inet4Address)addr;
+ }
+ }
+ }
+
+ InetAddress localhost = InetAddress.getLocalHost();
+ if (localhost instanceof Inet4Address) {
+ return (Inet4Address)localhost;
+ }
+
+ throw new UnsupportedAddressTypeException();
+ }
+
+ /**
+ * Display program usage on the given {@link PrintStream}.
+ */
+ private static void usage(PrintStream s) {
+ s.println("usage: Client [options] ");
+ s.println();
+ s.println("Available options:");
+ s.println(" -h,--help Show this help and exit.");
+ s.println(" -o,--output DIR Read/write data to directory DIR.");
+ s.println(" -i,--iface IFACE Bind to interface IFACE.");
+ s.println(" -s,--seed SECONDS Time to seed after downloading (default: infinitely).");
+ s.println(" -d,--max-download KB/SEC Max download rate (default: unlimited).");
+ s.println(" -u,--max-upload KB/SEC Max upload rate (default: unlimited).");
+ s.println();
+ }
+
+ /**
+ * Main client entry point for stand-alone operation.
+ */
+ public static void main(String[] args) {
+ BasicConfigurator.configure(new ConsoleAppender(
+ new PatternLayout("%d [%-25t] %-5p: %m%n")));
+
+ CmdLineParser parser = new CmdLineParser();
+ CmdLineParser.Option help = parser.addBooleanOption('h', "help");
+ CmdLineParser.Option output = parser.addStringOption('o', "output");
+ CmdLineParser.Option iface = parser.addStringOption('i', "iface");
+ CmdLineParser.Option seedTime = parser.addIntegerOption('s', "seed");
+ CmdLineParser.Option maxUpload = parser.addDoubleOption('u', "max-upload");
+ CmdLineParser.Option maxDownload = parser.addDoubleOption('d', "max-download");
+
+ try {
+ parser.parse(args);
+ } catch (CmdLineParser.OptionException oe) {
+ System.err.println(oe.getMessage());
+ usage(System.err);
+ System.exit(1);
+ }
+
+ // Display help and exit if requested
+ if (Boolean.TRUE.equals((Boolean)parser.getOptionValue(help))) {
+ usage(System.out);
+ System.exit(0);
+ }
+
+ String outputValue = (String)parser.getOptionValue(output,
+ DEFAULT_OUTPUT_DIRECTORY);
+ String ifaceValue = (String)parser.getOptionValue(iface);
+ int seedTimeValue = (Integer)parser.getOptionValue(seedTime, -1);
+
+ double maxDownloadRate = (Double)parser.getOptionValue(maxDownload, 0.0);
+ double maxUploadRate = (Double)parser.getOptionValue(maxUpload, 0.0);
+
+ String[] otherArgs = parser.getRemainingArgs();
+ if (otherArgs.length != 1) {
+ usage(System.err);
+ System.exit(1);
+ }
+
+ try {
+ Client c = new Client(
+ getIPv4Address(ifaceValue),
+ SharedTorrent.fromFile(
+ new File(otherArgs[0]),
+ new File(outputValue)));
+
+ c.setMaxDownloadRate(maxDownloadRate);
+ c.setMaxUploadRate(maxUploadRate);
+
+ // Set a shutdown hook that will stop the sharing/seeding and send
+ // a STOPPED announce request.
+ Runtime.getRuntime().addShutdownHook(
+ new Thread(new Client.ClientShutdown(c, null)));
+
+ c.share(seedTimeValue);
+ if (Client.ClientState.ERROR.equals(c.getState())) {
+ System.exit(1);
+ }
+ } catch (Exception e) {
+ logger.error("Fatal error: {}", e.getMessage(), e);
+ System.exit(2);
+ }
+ }
+}
diff --git a/src/main/java/com/turn/ttorrent/cli/TorrentMain.java b/src/main/java/com/turn/ttorrent/cli/TorrentMain.java
new file mode 100644
index 000000000..62b87bbc1
--- /dev/null
+++ b/src/main/java/com/turn/ttorrent/cli/TorrentMain.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright (C) 2011-2013 Turn, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.turn.ttorrent.cli;
+
+import com.turn.ttorrent.common.Torrent;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.URI;
+import java.util.Arrays;
+
+import jargs.gnu.CmdLineParser;
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.PatternLayout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Command-line entry-point for reading and writing {@link Torrent} files.
+ */
+public class TorrentMain {
+
+ private static final Logger logger =
+ LoggerFactory.getLogger(TorrentMain.class);
+
+ /**
+ * Display program usage on the given {@link PrintStream}.
+ */
+ private static void usage(PrintStream s) {
+ usage(s, null);
+ }
+
+ /**
+ * Display a message and program usage on the given {@link PrintStream}.
+ */
+ private static void usage(PrintStream s, String msg) {
+ if (msg != null) {
+ s.println(msg);
+ s.println();
+ }
+
+ s.println("usage: Torrent [options] [file|directory]");
+ s.println();
+ s.println("Available options:");
+ s.println(" -h,--help Show this help and exit.");
+ s.println(" -t,--torrent FILE Use FILE to read/write torrent file.");
+ s.println();
+ s.println(" -c,--create Create a new torrent file using " +
+ "the given announce URL and data.");
+ s.println(" -a,--announce Tracker URL (can be repeated).");
+ s.println();
+ }
+
+ /**
+ * Torrent reader and creator.
+ *
+ *
+ * You can use the {@code main()} function of this class to read or create
+ * torrent files. See usage for details.
+ *
+ *
+ * TODO: support multiple announce URLs.
+ */
+ public static void main(String[] args) {
+ BasicConfigurator.configure(new ConsoleAppender(
+ new PatternLayout("%-5p: %m%n")));
+
+ CmdLineParser parser = new CmdLineParser();
+ CmdLineParser.Option help = parser.addBooleanOption('h', "help");
+ CmdLineParser.Option filename = parser.addStringOption('t', "torrent");
+ CmdLineParser.Option create = parser.addBooleanOption('c', "create");
+ CmdLineParser.Option announce = parser.addStringOption('a', "announce");
+
+ try {
+ parser.parse(args);
+ } catch (CmdLineParser.OptionException oe) {
+ System.err.println(oe.getMessage());
+ usage(System.err);
+ System.exit(1);
+ }
+
+ // Display help and exit if requested
+ if (Boolean.TRUE.equals((Boolean)parser.getOptionValue(help))) {
+ usage(System.out);
+ System.exit(0);
+ }
+
+ String filenameValue = (String)parser.getOptionValue(filename);
+ if (filenameValue == null) {
+ usage(System.err, "Torrent file must be provided!");
+ System.exit(1);
+ }
+
+ Boolean createFlag = (Boolean)parser.getOptionValue(create);
+ String announceURL = (String)parser.getOptionValue(announce);
+
+ String[] otherArgs = parser.getRemainingArgs();
+
+ if (Boolean.TRUE.equals(createFlag) &&
+ (otherArgs.length != 1 || announceURL == null)) {
+ usage(System.err, "Announce URL and a file or directory must be " +
+ "provided to create a torrent file!");
+ System.exit(1);
+ }
+
+ OutputStream fos = null;
+ try {
+ if (Boolean.TRUE.equals(createFlag)) {
+ if (filenameValue != null) {
+ fos = new FileOutputStream(filenameValue);
+ } else {
+ fos = System.out;
+ }
+
+ URI announceURI = new URI(announceURL);
+ File source = new File(otherArgs[0]);
+ if (!source.exists() || !source.canRead()) {
+ throw new IllegalArgumentException(
+ "Cannot access source file or directory " +
+ source.getName());
+ }
+
+ String creator = String.format("%s (ttorrent)",
+ System.getProperty("user.name"));
+
+ Torrent torrent = null;
+ if (source.isDirectory()) {
+ File[] files = source.listFiles();
+ Arrays.sort(files);
+ torrent = Torrent.create(source, Arrays.asList(files),
+ announceURI, creator);
+ } else {
+ torrent = Torrent.create(source, announceURI, creator);
+ }
+
+ torrent.save(fos);
+ } else {
+ Torrent.load(new File(filenameValue), true);
+ }
+ } catch (Exception e) {
+ logger.error("{}", e.getMessage(), e);
+ System.exit(2);
+ } finally {
+ if (fos != System.out) {
+ IOUtils.closeQuietly(fos);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/turn/ttorrent/cli/TrackerMain.java b/src/main/java/com/turn/ttorrent/cli/TrackerMain.java
new file mode 100644
index 000000000..bd3ffa473
--- /dev/null
+++ b/src/main/java/com/turn/ttorrent/cli/TrackerMain.java
@@ -0,0 +1,118 @@
+/**
+ * Copyright (C) 2011-2013 Turn, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.turn.ttorrent.cli;
+
+import com.turn.ttorrent.tracker.TrackedTorrent;
+import com.turn.ttorrent.tracker.Tracker;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+
+import jargs.gnu.CmdLineParser;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.PatternLayout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Command-line entry-point for starting a {@link Tracker}
+ */
+public class TrackerMain {
+
+ private static final Logger logger =
+ LoggerFactory.getLogger(TrackerMain.class);
+
+ /**
+ * Display program usage on the given {@link PrintStream}.
+ */
+ private static void usage(PrintStream s) {
+ s.println("usage: Tracker [options] [directory]");
+ s.println();
+ s.println("Available options:");
+ s.println(" -h,--help Show this help and exit.");
+ s.println(" -p,--port PORT Bind to port PORT.");
+ s.println();
+ }
+
+ /**
+ * Main function to start a tracker.
+ */
+ public static void main(String[] args) {
+ BasicConfigurator.configure(new ConsoleAppender(
+ new PatternLayout("%d [%-25t] %-5p: %m%n")));
+
+ CmdLineParser parser = new CmdLineParser();
+ CmdLineParser.Option help = parser.addBooleanOption('h', "help");
+ CmdLineParser.Option port = parser.addIntegerOption('p', "port");
+
+ try {
+ parser.parse(args);
+ } catch (CmdLineParser.OptionException oe) {
+ System.err.println(oe.getMessage());
+ usage(System.err);
+ System.exit(1);
+ }
+
+ // Display help and exit if requested
+ if (Boolean.TRUE.equals((Boolean)parser.getOptionValue(help))) {
+ usage(System.out);
+ System.exit(0);
+ }
+
+ Integer portValue = (Integer)parser.getOptionValue(port,
+ Integer.valueOf(Tracker.DEFAULT_TRACKER_PORT));
+
+ String[] otherArgs = parser.getRemainingArgs();
+
+ if (otherArgs.length > 1) {
+ usage(System.err);
+ System.exit(1);
+ }
+
+ // Get directory from command-line argument or default to current
+ // directory
+ String directory = otherArgs.length > 0
+ ? otherArgs[0]
+ : ".";
+
+ FilenameFilter filter = new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".torrent");
+ }
+ };
+
+ try {
+ Tracker t = new Tracker(new InetSocketAddress(portValue.intValue()));
+
+ File parent = new File(directory);
+ for (File f : parent.listFiles(filter)) {
+ logger.info("Loading torrent from " + f.getName());
+ t.announce(TrackedTorrent.load(f));
+ }
+
+ logger.info("Starting tracker with {} announced torrents...",
+ t.getTrackedTorrents().size());
+ t.start();
+ } catch (Exception e) {
+ logger.error("{}", e.getMessage(), e);
+ System.exit(2);
+ }
+ }
+}
diff --git a/src/main/java/com/turn/ttorrent/client/Client.java b/src/main/java/com/turn/ttorrent/client/Client.java
index 480657dbf..359ade64b 100644
--- a/src/main/java/com/turn/ttorrent/client/Client.java
+++ b/src/main/java/com/turn/ttorrent/client/Client.java
@@ -19,26 +19,19 @@
import com.turn.ttorrent.client.announce.AnnounceException;
import com.turn.ttorrent.client.announce.AnnounceResponseListener;
import com.turn.ttorrent.client.peer.PeerActivityListener;
+import com.turn.ttorrent.client.peer.SharingPeer;
import com.turn.ttorrent.common.Peer;
import com.turn.ttorrent.common.Torrent;
import com.turn.ttorrent.common.protocol.PeerMessage;
import com.turn.ttorrent.common.protocol.TrackerMessage;
-import com.turn.ttorrent.client.peer.SharingPeer;
-import java.io.File;
import java.io.IOException;
-import java.io.PrintStream;
-import java.net.Inet4Address;
import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
-import java.nio.channels.UnsupportedAddressTypeException;
import java.util.BitSet;
import java.util.Comparator;
-import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Observable;
@@ -51,11 +44,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import jargs.gnu.CmdLineParser;
-
-import org.apache.log4j.BasicConfigurator;
-import org.apache.log4j.ConsoleAppender;
-import org.apache.log4j.PatternLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -99,9 +87,6 @@ public class Client extends Observable implements Runnable,
private static final int RATE_COMPUTATION_ITERATIONS = 2;
private static final int MAX_DOWNLOADERS_UNCHOKE = 4;
- /** Default data output directory. */
- private static final String DEFAULT_OUTPUT_DIRECTORY = "/tmp";
-
public enum ClientState {
WAITING,
VALIDATING,
@@ -172,6 +157,26 @@ public Client(InetAddress address, SharedTorrent torrent)
this.random = new Random(System.currentTimeMillis());
}
+ /**
+ * Set the maximum download rate (in kB/second) for this
+ * torrent. A setting of <= 0.0 disables rate limiting.
+ *
+ * @param rate The maximum download rate
+ */
+ public void setMaxDownloadRate(double rate) {
+ this.torrent.setMaxDownloadRate(rate);
+ }
+
+ /**
+ * Set the maximum upload rate (in kB/second) for this
+ * torrent. A setting of <= 0.0 disables rate limiting.
+ *
+ * @param rate The maximum upload rate
+ */
+ public void setMaxUploadRate(double rate) {
+ this.torrent.setMaxUploadRate(rate);
+ }
+
/**
* Get this client's peer specification.
*/
@@ -316,7 +321,8 @@ public void run() {
// First, analyze the torrent's local data.
try {
this.setState(ClientState.VALIDATING);
- this.torrent.init();
+ if (!this.torrent.isInitialized())
+ this.torrent.init();
} catch (IOException ioe) {
logger.warn("Error while initializing torrent data: {}!",
ioe.getMessage(), ioe);
@@ -921,11 +927,11 @@ public void handleIOException(SharingPeer peer, IOException ioe) {
*
* When the download is complete, the client switches to seeding mode for
* as long as requested in the share() call, if seeding was
- * requested. If not, the StopSeedingTask will execute immediately to stop
- * the client's main loop.
+ * requested. If not, the {@link ClientShutdown} will execute
+ * immediately to stop the client's main loop.
*
*
- * @see StopSeedingTask
+ * @see ClientShutdown
*/
private synchronized void seed() {
// Silently ignore if we're already seeding.
@@ -965,12 +971,12 @@ private synchronized void seed() {
*
* @author mpetazzoni
*/
- private static class ClientShutdown extends TimerTask {
+ public static class ClientShutdown extends TimerTask {
private final Client client;
private final Timer timer;
- ClientShutdown(Client client, Timer timer) {
+ public ClientShutdown(Client client, Timer timer) {
this.client = client;
this.timer = timer;
}
@@ -983,120 +989,4 @@ public void run() {
}
}
};
-
- /**
- * Display program usage on the given {@link PrintStream}.
- */
- private static void usage(PrintStream s) {
- s.println("usage: Client [options] ");
- s.println();
- s.println("Available options:");
- s.println(" -h,--help Show this help and exit.");
- s.println(" -o,--output DIR Read/write data to directory DIR.");
- s.println(" -i,--iface IFACE Bind to interface IFACE.");
- s.println(" -s,--seed SECONDS Time to seed after downloading (default: infinitely).");
- s.println();
- }
-
- /**
- * Returns a usable {@link Inet4Address} for the given interface name.
- *
- *
- * If an interface name is given, return the first usable IPv4 address for
- * that interface. If no interface name is given or if that interface
- * doesn't have an IPv4 address, return's localhost address (if IPv4).
- *
- *
- *
- * It is understood this makes the client IPv4 only, but it is important to
- * remember that most BitTorrent extensions (like compact peer lists from
- * trackers and UDP tracker support) are IPv4-only anyway.
- *
- *
- * @param iface The network interface name.
- * @return A usable IPv4 address as a {@link Inet4Address}.
- * @throws UnsupportedAddressTypeException If no IPv4 address was available
- * to bind on.
- */
- private static Inet4Address getIPv4Address(String iface)
- throws SocketException, UnsupportedAddressTypeException,
- UnknownHostException {
- if (iface != null) {
- Enumeration addresses =
- NetworkInterface.getByName(iface).getInetAddresses();
- while (addresses.hasMoreElements()) {
- InetAddress addr = addresses.nextElement();
- if (addr instanceof Inet4Address) {
- return (Inet4Address)addr;
- }
- }
- }
-
- InetAddress localhost = InetAddress.getLocalHost();
- if (localhost instanceof Inet4Address) {
- return (Inet4Address)localhost;
- }
-
- throw new UnsupportedAddressTypeException();
- }
-
- /**
- * Main client entry point for stand-alone operation.
- */
- public static void main(String[] args) {
- BasicConfigurator.configure(new ConsoleAppender(
- new PatternLayout("%d [%-25t] %-5p: %m%n")));
-
- CmdLineParser parser = new CmdLineParser();
- CmdLineParser.Option help = parser.addBooleanOption('h', "help");
- CmdLineParser.Option output = parser.addStringOption('o', "output");
- CmdLineParser.Option iface = parser.addStringOption('i', "iface");
- CmdLineParser.Option seedTime = parser.addIntegerOption('s', "seed");
-
- try {
- parser.parse(args);
- } catch (CmdLineParser.OptionException oe) {
- System.err.println(oe.getMessage());
- usage(System.err);
- System.exit(1);
- }
-
- // Display help and exit if requested
- if (Boolean.TRUE.equals((Boolean)parser.getOptionValue(help))) {
- usage(System.out);
- System.exit(0);
- }
-
- String outputValue = (String)parser.getOptionValue(output,
- DEFAULT_OUTPUT_DIRECTORY);
- String ifaceValue = (String)parser.getOptionValue(iface);
- int seedTimeValue = (Integer)parser.getOptionValue(seedTime, -1);
-
- String[] otherArgs = parser.getRemainingArgs();
- if (otherArgs.length != 1) {
- usage(System.err);
- System.exit(1);
- }
-
- try {
- Client c = new Client(
- getIPv4Address(ifaceValue),
- SharedTorrent.fromFile(
- new File(otherArgs[0]),
- new File(outputValue)));
-
- // Set a shutdown hook that will stop the sharing/seeding and send
- // a STOPPED announce request.
- Runtime.getRuntime().addShutdownHook(
- new Thread(new ClientShutdown(c, null)));
-
- c.share(seedTimeValue);
- if (ClientState.ERROR.equals(c.getState())) {
- System.exit(1);
- }
- } catch (Exception e) {
- logger.error("Fatal error: {}", e.getMessage(), e);
- System.exit(2);
- }
- }
}
diff --git a/src/main/java/com/turn/ttorrent/client/ConnectionHandler.java b/src/main/java/com/turn/ttorrent/client/ConnectionHandler.java
index 8b56336c9..4653f9466 100644
--- a/src/main/java/com/turn/ttorrent/client/ConnectionHandler.java
+++ b/src/main/java/com/turn/ttorrent/client/ConnectionHandler.java
@@ -36,6 +36,7 @@
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -309,16 +310,12 @@ private void accept(SocketChannel client)
} catch (ParseException pe) {
logger.info("Invalid handshake from {}: {}",
this.socketRepr(client), pe.getMessage());
- try { client.close(); } catch (IOException e) { }
+ IOUtils.closeQuietly(client);
} catch (IOException ioe) {
logger.warn("An error occured while reading an incoming " +
"handshake: {}", ioe.getMessage());
- try {
- if (client.isConnected()) {
- client.close();
- }
- } catch (IOException e) {
- // Ignore
+ if (client.isConnected()) {
+ IOUtils.closeQuietly(client);
}
}
}
@@ -456,7 +453,7 @@ public Thread newThread(Runnable r) {
t.setName("bt-connect-" + ++this.number);
return t;
}
- };
+ }
/**
@@ -510,15 +507,11 @@ public void run() {
channel.configureBlocking(false);
this.handler.fireNewPeerConnection(channel, hs.getPeerId());
} catch (Exception e) {
- try {
- if (channel != null && channel.isConnected()) {
- channel.close();
- }
- } catch (IOException ioe) {
- // Ignore
+ if (channel != null && channel.isConnected()) {
+ IOUtils.closeQuietly(channel);
}
this.handler.fireFailedConnection(this.peer, e);
}
}
- };
+ }
}
diff --git a/src/main/java/com/turn/ttorrent/client/Piece.java b/src/main/java/com/turn/ttorrent/client/Piece.java
index 77d60640b..5cd8534ac 100644
--- a/src/main/java/com/turn/ttorrent/client/Piece.java
+++ b/src/main/java/com/turn/ttorrent/client/Piece.java
@@ -21,7 +21,6 @@
import java.io.IOException;
import java.nio.ByteBuffer;
-import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.concurrent.Callable;
@@ -159,16 +158,10 @@ public synchronized boolean validate() throws IOException {
logger.trace("Validating {}...", this);
this.valid = false;
- try {
- // TODO: remove cast to int when large ByteBuffer support is
- // implemented in Java.
- ByteBuffer buffer = this._read(0, this.length);
- byte[] data = new byte[(int)this.length];
- buffer.get(data);
- this.valid = Arrays.equals(Torrent.hash(data), this.hash);
- } catch (NoSuchAlgorithmException nsae) {
- logger.error("{}", nsae);
- }
+ ByteBuffer buffer = this._read(0, this.length);
+ byte[] data = new byte[(int)this.length];
+ buffer.get(data);
+ this.valid = Arrays.equals(Torrent.hash(data), this.hash);
return this.isValid();
}
@@ -283,15 +276,11 @@ public String toString() {
* @param other The piece to compare with, should not be null.
*/
public int compareTo(Piece other) {
- if (this == other) {
- return 0;
- }
-
- if (this.seen < other.seen) {
- return -1;
- } else {
- return 1;
+ if (this.seen != other.seen) {
+ return this.seen < other.seen ? -1 : 1;
}
+ return this.index == other.index ? 0 :
+ (this.index < other.index ? -1 : 1);
}
/**
diff --git a/src/main/java/com/turn/ttorrent/client/SharedTorrent.java b/src/main/java/com/turn/ttorrent/client/SharedTorrent.java
index 85f4ac310..82280586b 100644
--- a/src/main/java/com/turn/ttorrent/client/SharedTorrent.java
+++ b/src/main/java/com/turn/ttorrent/client/SharedTorrent.java
@@ -18,18 +18,17 @@
import com.turn.ttorrent.bcodec.InvalidBEncodingException;
import com.turn.ttorrent.common.Torrent;
import com.turn.ttorrent.client.peer.PeerActivityListener;
+import com.turn.ttorrent.client.peer.Rate;
import com.turn.ttorrent.client.peer.SharingPeer;
import com.turn.ttorrent.client.storage.TorrentByteStorage;
import com.turn.ttorrent.client.storage.FileStorage;
import com.turn.ttorrent.client.storage.FileCollectionStorage;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
-import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
@@ -43,6 +42,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -95,11 +95,14 @@ public class SharedTorrent extends Torrent implements PeerActivityListener {
private final ByteBuffer piecesHashes;
private boolean initialized;
+ private boolean peerOnly;
private Piece[] pieces;
private SortedSet rarest;
private BitSet completedPieces;
private BitSet requestedPieces;
-
+
+ private double maxUploadRate = 0.0;
+ private double maxDownloadRate = 0.0;
/**
* Create a new shared torrent from a base Torrent object.
*
@@ -114,10 +117,9 @@ public class SharedTorrent extends Torrent implements PeerActivityListener {
* @throws FileNotFoundException If the torrent file location or
* destination directory does not exist and can't be created.
* @throws IOException If the torrent file cannot be read or decoded.
- * @throws NoSuchAlgorithmException
*/
public SharedTorrent(Torrent torrent, File destDir)
- throws FileNotFoundException, IOException, NoSuchAlgorithmException {
+ throws FileNotFoundException, IOException {
this(torrent, destDir, false);
}
@@ -137,10 +139,9 @@ public SharedTorrent(Torrent torrent, File destDir)
* @throws FileNotFoundException If the torrent file location or
* destination directory does not exist and can't be created.
* @throws IOException If the torrent file cannot be read or decoded.
- * @throws NoSuchAlgorithmException
*/
public SharedTorrent(Torrent torrent, File destDir, boolean seeder)
- throws FileNotFoundException, IOException, NoSuchAlgorithmException {
+ throws FileNotFoundException, IOException {
this(torrent.getEncoded(), destDir, seeder);
}
@@ -155,7 +156,7 @@ public SharedTorrent(Torrent torrent, File destDir, boolean seeder)
* @throws IOException If the torrent file cannot be read or decoded.
*/
public SharedTorrent(byte[] torrent, File destDir)
- throws FileNotFoundException, IOException, NoSuchAlgorithmException {
+ throws FileNotFoundException, IOException {
this(torrent, destDir, false);
}
@@ -169,20 +170,22 @@ public SharedTorrent(byte[] torrent, File destDir)
* @throws FileNotFoundException If the torrent file location or
* destination directory does not exist and can't be created.
* @throws IOException If the torrent file cannot be read or decoded.
- * @throws NoSuchAlgorithmException
- * @throws URISyntaxException When one of the defined tracker addresses is
- * invalid.
*/
public SharedTorrent(byte[] torrent, File parent, boolean seeder)
- throws FileNotFoundException, IOException, NoSuchAlgorithmException {
+ throws FileNotFoundException, IOException {
super(torrent, seeder);
- if (parent == null || !parent.isDirectory()) {
- throw new IllegalArgumentException("Invalid parent directory!");
+ this.peerOnly = false;
+ String parentPath = null;
+
+ if (parent == null) {
+ this.peerOnly = true;
+ } else if (!parent.isDirectory()) {
+ throw new IllegalArgumentException("Invalid parent directory!");
+ } else {
+ parentPath = parent.getCanonicalPath();
}
- String parentPath = parent.getCanonicalPath();
-
try {
this.pieceLength = this.decoded_info.get("piece length").getInt();
this.piecesHashes = ByteBuffer.wrap(this.decoded_info.get("pieces")
@@ -198,21 +201,25 @@ public SharedTorrent(byte[] torrent, File parent, boolean seeder)
"Error reading torrent meta-info fields!");
}
- List files = new LinkedList();
- long offset = 0L;
- for (Torrent.TorrentFile file : this.files) {
- File actual = new File(parent, file.file.getPath());
+ if (this.peerOnly) {
+ this.bucket = null;
+ } else {
+ List files = new LinkedList();
+ long offset = 0L;
+ for (Torrent.TorrentFile file : this.files) {
+ File actual = new File(parent, file.file.getPath());
+
+ if (!actual.getCanonicalPath().startsWith(parentPath)) {
+ throw new SecurityException("Torrent file path attempted " +
+ "to break directory jail!");
+ }
- if (!actual.getCanonicalPath().startsWith(parentPath)) {
- throw new SecurityException("Torrent file path attempted " +
- "to break directory jail!");
+ actual.getParentFile().mkdirs();
+ files.add(new FileStorage(actual, offset, file.size));
+ offset += file.size;
}
-
- actual.getParentFile().mkdirs();
- files.add(new FileStorage(actual, offset, file.size));
- offset += file.size;
+ this.bucket = new FileCollectionStorage(files, this.getSize());
}
- this.bucket = new FileCollectionStorage(files, this.getSize());
this.random = new Random(System.currentTimeMillis());
this.stop = false;
@@ -221,8 +228,21 @@ public SharedTorrent(byte[] torrent, File parent, boolean seeder)
this.downloaded = 0;
this.left = this.getSize();
- this.initialized = false;
- this.pieces = new Piece[0];
+ if (this.peerOnly) {
+ this.initialized = true;
+ int nPieces = (int) (Math.ceil(
+ (double)this.getSize() / this.pieceLength));
+ this.pieces = new Piece[nPieces];
+
+ for (int idx=0;idx());
this.completedPieces = new BitSet();
this.requestedPieces = new BitSet();
@@ -235,17 +255,41 @@ public SharedTorrent(byte[] torrent, File parent, boolean seeder)
* meta-info from.
* @param parent The parent directory or location of the torrent files.
* @throws IOException When the torrent file cannot be read or decoded.
- * @throws NoSuchAlgorithmException
*/
public static SharedTorrent fromFile(File source, File parent)
- throws IOException, NoSuchAlgorithmException {
- FileInputStream fis = new FileInputStream(source);
- byte[] data = new byte[(int)source.length()];
- fis.read(data);
- fis.close();
+ throws IOException {
+ byte[] data = FileUtils.readFileToByteArray(source);
return new SharedTorrent(data, parent);
}
+ public double getMaxUploadRate() {
+ return this.maxUploadRate;
+ }
+
+ /**
+ * Set the maximum upload rate (in kB/second) for this
+ * torrent. A setting of <= 0.0 disables rate limiting.
+ *
+ * @param rate The maximum upload rate
+ */
+ public void setMaxUploadRate(double rate) {
+ this.maxUploadRate = rate;
+ }
+
+ public double getMaxDownloadRate() {
+ return this.maxDownloadRate;
+ }
+
+ /**
+ * Set the maximum download rate (in kB/second) for this
+ * torrent. A setting of <= 0.0 disables rate limiting.
+ *
+ * @param rate The maximum download rate
+ */
+ public void setMaxDownloadRate(double rate) {
+ this.maxDownloadRate = rate;
+ }
+
/**
* Get the number of bytes uploaded for this torrent.
*/
@@ -453,7 +497,7 @@ public BitSet getAvailablePieces() {
synchronized (this.pieces) {
for (Piece piece : this.pieces) {
- if (piece.available()) {
+ if (piece != null && piece.available()) {
availablePieces.set(piece.getIndex());
}
}
@@ -727,9 +771,11 @@ public synchronized void handleBitfieldAvailability(SharingPeer peer,
// Record that the peer has all the pieces it told us it had.
for (int i = availablePieces.nextSetBit(0); i >= 0;
i = availablePieces.nextSetBit(i+1)) {
- this.rarest.remove(this.pieces[i]);
- this.pieces[i].seenAt(peer);
- this.rarest.add(this.pieces[i]);
+ if (this.pieces[i] != null) {
+ this.rarest.remove(this.pieces[i]);
+ this.pieces[i].seenAt(peer);
+ this.rarest.add(this.pieces[i]);
+ }
}
logger.trace("Peer {} contributes {} piece(s) ({} interesting) " +
diff --git a/src/main/java/com/turn/ttorrent/client/announce/Announce.java b/src/main/java/com/turn/ttorrent/client/announce/Announce.java
index 4c0a1b223..0146b011f 100644
--- a/src/main/java/com/turn/ttorrent/client/announce/Announce.java
+++ b/src/main/java/com/turn/ttorrent/client/announce/Announce.java
@@ -75,8 +75,6 @@ public class Announce implements Runnable {
*
* @param torrent The torrent we're announcing about.
* @param peer Our peer specification.
- * @param type A string representing the announce type (used in the thread
- * name).
*/
public Announce(SharedTorrent torrent, Peer peer) {
this.peer = peer;
@@ -226,7 +224,12 @@ public void run() {
event = AnnounceRequestMessage.RequestEvent.NONE;
} catch (AnnounceException ae) {
logger.warn(ae.getMessage());
- this.moveToNextTrackerClient();
+
+ try {
+ this.moveToNextTrackerClient();
+ } catch (AnnounceException e) {
+ logger.error("Unable to move to the next tracker client: {}", e.getMessage());
+ }
}
try {
@@ -281,8 +284,15 @@ private TrackerClient createTrackerClient(SharedTorrent torrent, Peer peer,
/**
* Returns the current tracker client used for announces.
+ * @throws AnnounceException When the current announce tier isn't defined
+ * in the torrent.
*/
- public TrackerClient getCurrentTrackerClient() {
+ public TrackerClient getCurrentTrackerClient() throws AnnounceException {
+ if ((this.currentTier >= this.clients.size()) ||
+ (this.currentClient >= this.clients.get(this.currentTier).size())) {
+ throw new AnnounceException("Current tier or client isn't available");
+ }
+
return this.clients
.get(this.currentTier)
.get(this.currentClient);
@@ -300,8 +310,10 @@ public TrackerClient getCurrentTrackerClient() {
* The index of the currently used {@link TrackerClient} is reset to 0 to
* reflect this change.
*
+ *
+ * @throws AnnounceException
*/
- private void promoteCurrentTrackerClient() {
+ private void promoteCurrentTrackerClient() throws AnnounceException {
logger.trace("Promoting current tracker client for {} " +
"(tier {}, position {} -> 0).",
new Object[] {
@@ -327,8 +339,10 @@ private void promoteCurrentTrackerClient() {
* By design no empty tier can be in the tracker list structure so we don't
* need to check for empty tiers here.
*
+ *
+ * @throws AnnounceException
*/
- private void moveToNextTrackerClient() {
+ private void moveToNextTrackerClient() throws AnnounceException {
int tier = this.currentTier;
int client = this.currentClient + 1;
diff --git a/src/main/java/com/turn/ttorrent/client/announce/TrackerClient.java b/src/main/java/com/turn/ttorrent/client/announce/TrackerClient.java
index b757e0e17..7cfa0ece4 100644
--- a/src/main/java/com/turn/ttorrent/client/announce/TrackerClient.java
+++ b/src/main/java/com/turn/ttorrent/client/announce/TrackerClient.java
@@ -83,7 +83,7 @@ public abstract void announce(AnnounceRequestMessage.RequestEvent event,
* Close any opened announce connection.
*
*
- * This method is called by {@link #stop()} to make sure all connections
+ * This method is called by {@link Announce#stop()} to make sure all connections
* are correctly closed when the announce thread is asked to stop.
*
*/
diff --git a/src/main/java/com/turn/ttorrent/client/announce/UDPTrackerClient.java b/src/main/java/com/turn/ttorrent/client/announce/UDPTrackerClient.java
index 01b1c0420..5df3229b0 100644
--- a/src/main/java/com/turn/ttorrent/client/announce/UDPTrackerClient.java
+++ b/src/main/java/com/turn/ttorrent/client/announce/UDPTrackerClient.java
@@ -239,7 +239,7 @@ public void announce(AnnounceRequestMessage.RequestEvent event,
*
*
* Verifies the transaction ID of the message before passing it over to
- * {@link Announce#handleTrackerAnnounceResponse()}.
+ * any registered {@link AnnounceResponseListener}.
*
*
* @param message The message received from the tracker in response to the
@@ -352,7 +352,7 @@ private void send(ByteBuffer data) {
*
* @param attempt The attempt number, used to calculate the timeout for the
* receive operation.
- * @retun Returns a {@link ByteBuffer} containing the packet data.
+ * @return Returns a {@link ByteBuffer} containing the packet data.
*/
private ByteBuffer recv(int attempt)
throws IOException, SocketException, SocketTimeoutException {
diff --git a/src/main/java/com/turn/ttorrent/client/peer/PeerExchange.java b/src/main/java/com/turn/ttorrent/client/peer/PeerExchange.java
index a7448d00c..b76012e25 100644
--- a/src/main/java/com/turn/ttorrent/client/peer/PeerExchange.java
+++ b/src/main/java/com/turn/ttorrent/client/peer/PeerExchange.java
@@ -17,6 +17,7 @@
import com.turn.ttorrent.client.SharedTorrent;
import com.turn.ttorrent.common.protocol.PeerMessage;
+import com.turn.ttorrent.common.protocol.PeerMessage.Type;
import java.io.EOFException;
import java.io.IOException;
@@ -32,6 +33,7 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -177,16 +179,73 @@ public void close() {
this.stop = true;
if (this.channel.isConnected()) {
- try {
- this.channel.close();
- } catch (IOException ioe) {
- // Ignore
- }
+ IOUtils.closeQuietly(this.channel);
}
logger.debug("Peer exchange with {} closed.", this.peer);
}
+ /**
+ * Abstract Thread subclass that allows conditional rate limiting
+ * for PIECE messages.
+ *
+ *
+ * To impose rate limits, we only want to throttle when processing PIECE
+ * messages. All other peer messages should be exchanged as quickly as
+ * possible.
+ *
+ *
+ * @author ptgoetz
+ */
+ private abstract class RateLimitThread extends Thread {
+
+ protected final Rate rate = new Rate();
+ protected long sleep = 1000;
+
+ /**
+ * Dynamically determines an amount of time to sleep, based on the
+ * average read/write throughput.
+ *
+ *
+ * The algorithm is functional, but could certainly be improved upon.
+ * One obvious drawback is that with large changes in
+ * maxRate, it will take a while for the sleep time to
+ * adjust and the throttled rate to "smooth out."
+ *
+ *
+ *
+ * Ideally, it would calculate the optimal sleep time necessary to hit
+ * a desired throughput rather than continuously adjust toward a goal.
+ *
+ *
+ * @param maxRate the target rate in kB/second.
+ * @param messageSize the size, in bytes, of the last message read/written.
+ * @param message the last PeerMessage read/written.
+ */
+ protected void rateLimit(double maxRate, long messageSize, PeerMessage message) {
+ if (message.getType() != Type.PIECE || maxRate <= 0) {
+ return;
+ }
+
+ try {
+ this.rate.add(messageSize);
+
+ // Continuously adjust the sleep time to try to hit our target
+ // rate limit.
+ if (rate.get() > (maxRate * 1024)) {
+ Thread.sleep(this.sleep);
+ this.sleep += 50;
+ } else {
+ this.sleep = this.sleep > 50
+ ? this.sleep - 50
+ : 0;
+ }
+ } catch (InterruptedException e) {
+ // Not critical, eat it.
+ }
+ }
+ }
+
/**
* Incoming messages thread.
*
@@ -199,7 +258,7 @@ public void close() {
*
* @author mpetazzoni
*/
- private class IncomingThread extends Thread {
+ private class IncomingThread extends RateLimitThread {
@Override
public void run() {
@@ -210,28 +269,28 @@ public void run() {
buffer.rewind();
buffer.limit(PeerMessage.MESSAGE_LENGTH_FIELD_SIZE);
- if (channel.read(buffer) < 0) {
- throw new EOFException(
- "Reached end-of-stream while reading size header");
- }
-
// Keep reading bytes until the length field has been read
// entirely.
- if (buffer.hasRemaining()) {
+ while (!stop && buffer.hasRemaining()) {
+ if (channel.read(buffer) < 0) {
+ throw new EOFException(
+ "Reached end-of-stream while reading size header");
+ }
try {
Thread.sleep(1);
} catch (InterruptedException ie) {
// Ignore and move along.
}
-
- continue;
}
int pstrlen = buffer.getInt(0);
buffer.limit(PeerMessage.MESSAGE_LENGTH_FIELD_SIZE + pstrlen);
+ long size = 0;
while (!stop && buffer.hasRemaining()) {
- if (channel.read(buffer) < 0) {
+ int read = channel.read(buffer);
+ size += read;
+ if (read < 0) {
throw new EOFException(
"Reached end-of-stream while reading message");
}
@@ -243,6 +302,10 @@ public void run() {
PeerMessage message = PeerMessage.parse(buffer, torrent);
logger.trace("Received {} from {}", message, peer);
+ // Wait if needed to reach configured download rate.
+ this.rateLimit(PeerExchange.this.torrent.getMaxDownloadRate(),
+ size, message);
+
for (MessageListener listener : listeners) {
listener.handleMessage(message);
}
@@ -277,7 +340,7 @@ public void run() {
*
* @author mpetazzoni
*/
- private class OutgoingThread extends Thread {
+ private class OutgoingThread extends RateLimitThread {
@Override
public void run() {
@@ -302,12 +365,19 @@ public void run() {
logger.trace("Sending {} to {}", message, peer);
ByteBuffer data = message.getData();
+ long size = 0;
while (!stop && data.hasRemaining()) {
- if (channel.write(data) < 0) {
+ int written = channel.write(data);
+ size += written;
+ if (written < 0) {
throw new EOFException(
"Reached end of stream while writing");
}
}
+
+ // Wait if needed to reach configured upload rate.
+ this.rateLimit(PeerExchange.this.torrent.getMaxUploadRate(),
+ size, message);
} catch (InterruptedException ie) {
// Ignore and potentially terminate
}
diff --git a/src/main/java/com/turn/ttorrent/client/storage/FileCollectionStorage.java b/src/main/java/com/turn/ttorrent/client/storage/FileCollectionStorage.java
index c753132a6..6bb50d3b6 100644
--- a/src/main/java/com/turn/ttorrent/client/storage/FileCollectionStorage.java
+++ b/src/main/java/com/turn/ttorrent/client/storage/FileCollectionStorage.java
@@ -182,7 +182,7 @@ private List select(long offset, long length) {
long bytes = 0;
for (FileStorage file : this.files) {
- if (file.offset() > offset + length) {
+ if (file.offset() >= offset + length) {
break;
}
diff --git a/src/main/java/com/turn/ttorrent/common/Torrent.java b/src/main/java/com/turn/ttorrent/common/Torrent.java
index e468dc560..5ee4f471c 100644
--- a/src/main/java/com/turn/ttorrent/common/Torrent.java
+++ b/src/main/java/com/turn/ttorrent/common/Torrent.java
@@ -23,18 +23,14 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@@ -51,12 +47,9 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import jargs.gnu.CmdLineParser;
-
-import org.apache.log4j.BasicConfigurator;
-import org.apache.log4j.ConsoleAppender;
-import org.apache.log4j.PatternLayout;
-
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -104,7 +97,7 @@ public TorrentFile(File file, long size) {
this.file = file;
this.size = size;
}
- };
+ }
protected final byte[] encoded;
@@ -133,15 +126,11 @@ public TorrentFile(File file, long size) {
* BitTorrent specification) and create a Torrent object from it.
*
* @param torrent The meta-info byte data.
- * @param parent The parent directory or location of the torrent files.
* @param seeder Whether we'll be seeding for this torrent or not.
* @throws IOException When the info dictionary can't be read or
* encoded and hashed back to create the torrent's SHA-1 hash.
- * @throws NoSuchAlgorithmException If the SHA-1 algorithm is not
- * available.
*/
- public Torrent(byte[] torrent, boolean seeder)
- throws IOException, NoSuchAlgorithmException {
+ public Torrent(byte[] torrent, boolean seeder) throws IOException {
this.encoded = torrent;
this.seeder = seeder;
@@ -412,10 +401,8 @@ public void save(OutputStream output) throws IOException {
output.write(this.getEncoded());
}
- public static byte[] hash(byte[] data) throws NoSuchAlgorithmException {
- MessageDigest md = MessageDigest.getInstance("SHA-1");
- md.update(data);
- return md.digest();
+ public static byte[] hash(byte[] data) {
+ return DigestUtils.sha1(data);
}
/**
@@ -425,8 +412,7 @@ public static byte[] hash(byte[] data) throws NoSuchAlgorithmException {
* @param bytes The byte array to convert.
*/
public static String byteArrayToHexString(byte[] bytes) {
- BigInteger bi = new BigInteger(1, bytes);
- return String.format("%0" + (bytes.length << 1) + "X", bi);
+ return new String(Hex.encodeHex(bytes, false));
}
/**
@@ -485,10 +471,8 @@ protected static int getHashingThreadsCount() {
* @param torrent The abstract {@link File} object representing the
* .torrent file to load.
* @throws IOException When the torrent file cannot be read.
- * @throws NoSuchAlgorithmException
*/
- public static Torrent load(File torrent)
- throws IOException, NoSuchAlgorithmException {
+ public static Torrent load(File torrent) throws IOException {
return Torrent.load(torrent, false);
}
@@ -500,21 +484,11 @@ public static Torrent load(File torrent)
* @param seeder Whether we are a seeder for this torrent or not (disables
* local data validation).
* @throws IOException When the torrent file cannot be read.
- * @throws NoSuchAlgorithmException
*/
public static Torrent load(File torrent, boolean seeder)
- throws IOException, NoSuchAlgorithmException {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(torrent);
- byte[] data = new byte[(int)torrent.length()];
- fis.read(data);
- return new Torrent(data, seeder);
- } finally {
- if (fis != null) {
- fis.close();
- }
- }
+ throws IOException {
+ byte[] data = FileUtils.readFileToByteArray(torrent);
+ return new Torrent(data, seeder);
}
/** Torrent creation --------------------------------------------------- */
@@ -534,7 +508,7 @@ public static Torrent load(File torrent, boolean seeder)
* torrent's creator.
*/
public static Torrent create(File source, URI announce, String createdBy)
- throws NoSuchAlgorithmException, InterruptedException, IOException {
+ throws InterruptedException, IOException {
return Torrent.create(source, null, announce, null, createdBy);
}
@@ -556,8 +530,7 @@ public static Torrent create(File source, URI announce, String createdBy)
* torrent's creator.
*/
public static Torrent create(File parent, List files, URI announce,
- String createdBy) throws NoSuchAlgorithmException,
- InterruptedException, IOException {
+ String createdBy) throws InterruptedException, IOException {
return Torrent.create(parent, files, announce, null, createdBy);
}
@@ -577,8 +550,7 @@ public static Torrent create(File parent, List files, URI announce,
* torrent's creator.
*/
public static Torrent create(File source, List> announceList,
- String createdBy) throws NoSuchAlgorithmException,
- InterruptedException, IOException {
+ String createdBy) throws InterruptedException, IOException {
return Torrent.create(source, null, null, announceList, createdBy);
}
@@ -592,7 +564,7 @@ public static Torrent create(File source, List> announceList,
* considering we'll be a full initial seeder for it.
*
*
- * @param parent The parent directory or location of the torrent files,
+ * @param source The parent directory or location of the torrent files,
* also used as the torrent's name.
* @param files The files to add into this torrent.
* @param announceList The announce URIs organized as tiers that will
@@ -602,7 +574,7 @@ public static Torrent create(File source, List> announceList,
*/
public static Torrent create(File source, List files,
List> announceList, String createdBy)
- throws NoSuchAlgorithmException, InterruptedException, IOException {
+ throws InterruptedException, IOException {
return Torrent.create(source, files, null, announceList, createdBy);
}
@@ -627,7 +599,7 @@ public static Torrent create(File source, List files,
*/
private static Torrent create(File parent, List files, URI announce,
List> announceList, String createdBy)
- throws NoSuchAlgorithmException, InterruptedException, IOException {
+ throws InterruptedException, IOException {
if (files == null || files.isEmpty()) {
logger.info("Creating single-file torrent for {}...",
parent.getName());
@@ -704,9 +676,8 @@ private static class CallableChunkHasher implements Callable {
private final MessageDigest md;
private final ByteBuffer data;
- CallableChunkHasher(ByteBuffer buffer)
- throws NoSuchAlgorithmException {
- this.md = MessageDigest.getInstance("SHA-1");
+ CallableChunkHasher(ByteBuffer buffer) {
+ this.md = DigestUtils.getSha1Digest();
this.data = ByteBuffer.allocate(buffer.remaining());
buffer.mark();
@@ -739,12 +710,12 @@ public String call() throws UnsupportedEncodingException {
* @param file The file to hash.
*/
private static String hashFile(File file)
- throws NoSuchAlgorithmException, InterruptedException, IOException {
+ throws InterruptedException, IOException {
return Torrent.hashFiles(Arrays.asList(new File[] { file }));
}
private static String hashFiles(List files)
- throws NoSuchAlgorithmException, InterruptedException, IOException {
+ throws InterruptedException, IOException {
int threads = getHashingThreadsCount();
ExecutorService executor = Executors.newFixedThreadPool(threads);
ByteBuffer buffer = ByteBuffer.allocate(Torrent.PIECE_LENGTH);
@@ -843,131 +814,4 @@ private static int accumulateHashes(StringBuilder hashes,
throw new IOException("Error while hashing the torrent data!", ee);
}
}
-
- /**
- * Display program usage on the given {@link PrintStream}.
- */
- private static void usage(PrintStream s) {
- usage(s, null);
- }
-
- /**
- * Display a message and program usage on the given {@link PrintStream}.
- */
- private static void usage(PrintStream s, String msg) {
- if (msg != null) {
- s.println(msg);
- s.println();
- }
-
- s.println("usage: Torrent [options] [file|directory]");
- s.println();
- s.println("Available options:");
- s.println(" -h,--help Show this help and exit.");
- s.println(" -t,--torrent FILE Use FILE to read/write torrent file.");
- s.println();
- s.println(" -c,--create Create a new torrent file using " +
- "the given announce URL and data.");
- s.println(" -a,--announce Tracker URL (can be repeated).");
- s.println();
- }
-
- /**
- * Torrent reader and creator.
- *
- *
- * You can use the {@code main()} function of this {@link Torrent} class to
- * read or create torrent files. See usage for details.
- *
- *
- * TODO: support multiple announce URLs.
- */
- public static void main(String[] args) {
- BasicConfigurator.configure(new ConsoleAppender(
- new PatternLayout("%-5p: %m%n")));
-
- CmdLineParser parser = new CmdLineParser();
- CmdLineParser.Option help = parser.addBooleanOption('h', "help");
- CmdLineParser.Option filename = parser.addStringOption('t', "torrent");
- CmdLineParser.Option create = parser.addBooleanOption('c', "create");
- CmdLineParser.Option announce = parser.addStringOption('a', "announce");
-
- try {
- parser.parse(args);
- } catch (CmdLineParser.OptionException oe) {
- System.err.println(oe.getMessage());
- usage(System.err);
- System.exit(1);
- }
-
- // Display help and exit if requested
- if (Boolean.TRUE.equals((Boolean)parser.getOptionValue(help))) {
- usage(System.out);
- System.exit(0);
- }
-
- String filenameValue = (String)parser.getOptionValue(filename);
- if (filenameValue == null) {
- usage(System.err, "Torrent file must be provided!");
- System.exit(1);
- }
-
- Boolean createFlag = (Boolean)parser.getOptionValue(create);
- String announceURL = (String)parser.getOptionValue(announce);
-
- String[] otherArgs = parser.getRemainingArgs();
-
- if (Boolean.TRUE.equals(createFlag) &&
- (otherArgs.length != 1 || announceURL == null)) {
- usage(System.err, "Announce URL and a file or directory must be " +
- "provided to create a torrent file!");
- System.exit(1);
- }
-
- OutputStream fos = null;
- try {
- if (Boolean.TRUE.equals(createFlag)) {
- if (filenameValue != null) {
- fos = new FileOutputStream(filenameValue);
- } else {
- fos = System.out;
- }
-
- URI announceURI = new URI(announceURL);
- File source = new File(otherArgs[0]);
- if (!source.exists() || !source.canRead()) {
- throw new IllegalArgumentException(
- "Cannot access source file or directory " +
- source.getName());
- }
-
- String creator = String.format("%s (ttorrent)",
- System.getProperty("user.name"));
-
- Torrent torrent = null;
- if (source.isDirectory()) {
- File[] files = source.listFiles();
- Arrays.sort(files);
- torrent = Torrent.create(source, Arrays.asList(files),
- announceURI, creator);
- } else {
- torrent = Torrent.create(source, announceURI, creator);
- }
-
- torrent.save(fos);
- } else {
- Torrent.load(new File(filenameValue), true);
- }
- } catch (Exception e) {
- logger.error("{}", e.getMessage(), e);
- System.exit(2);
- } finally {
- if (fos != null && fos != System.out) {
- try {
- fos.close();
- } catch (IOException ioe) {
- }
- }
- }
- }
}
diff --git a/src/main/java/com/turn/ttorrent/common/protocol/http/HTTPAnnounceRequestMessage.java b/src/main/java/com/turn/ttorrent/common/protocol/http/HTTPAnnounceRequestMessage.java
index 0362ba72f..3645573d0 100644
--- a/src/main/java/com/turn/ttorrent/common/protocol/http/HTTPAnnounceRequestMessage.java
+++ b/src/main/java/com/turn/ttorrent/common/protocol/http/HTTPAnnounceRequestMessage.java
@@ -254,7 +254,7 @@ public static HTTPAnnounceRequestMessage parse(ByteBuffer data)
return new HTTPAnnounceRequestMessage(data, infoHash,
new Peer(ip, port, ByteBuffer.wrap(peerId)),
- downloaded, uploaded, left, compact, noPeerId,
+ uploaded, downloaded, left, compact, noPeerId,
event, numWant);
} catch (InvalidBEncodingException ibee) {
throw new MessageValidationException(
diff --git a/src/main/java/com/turn/ttorrent/common/protocol/http/HTTPAnnounceResponseMessage.java b/src/main/java/com/turn/ttorrent/common/protocol/http/HTTPAnnounceResponseMessage.java
index efb75fce3..01d27cd7f 100644
--- a/src/main/java/com/turn/ttorrent/common/protocol/http/HTTPAnnounceResponseMessage.java
+++ b/src/main/java/com/turn/ttorrent/common/protocol/http/HTTPAnnounceResponseMessage.java
@@ -87,6 +87,11 @@ public static HTTPAnnounceResponseMessage parse(ByteBuffer data)
Map params = decoded.getMap();
+ if (params.get("interval") == null) {
+ throw new MessageValidationException(
+ "Tracker message missing mandatory field 'interval'!");
+ }
+
try {
List peers;
@@ -102,8 +107,8 @@ public static HTTPAnnounceResponseMessage parse(ByteBuffer data)
return new HTTPAnnounceResponseMessage(data,
params.get("interval").getInt(),
- params.get("complete").getInt(),
- params.get("incomplete").getInt(),
+ params.get("complete") != null ? params.get("complete").getInt() : 0,
+ params.get("incomplete") != null ? params.get("incomplete").getInt() : 0,
peers);
} catch (InvalidBEncodingException ibee) {
throw new MessageValidationException("Invalid response " +
diff --git a/src/main/java/com/turn/ttorrent/tracker/TrackedTorrent.java b/src/main/java/com/turn/ttorrent/tracker/TrackedTorrent.java
index 98a2734b5..3e8e500d6 100644
--- a/src/main/java/com/turn/ttorrent/tracker/TrackedTorrent.java
+++ b/src/main/java/com/turn/ttorrent/tracker/TrackedTorrent.java
@@ -20,12 +20,10 @@
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
-import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -33,6 +31,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -77,11 +76,8 @@ public class TrackedTorrent extends Torrent {
* @param torrent The meta-info byte data.
* @throws IOException When the info dictionary can't be
* encoded and hashed back to create the torrent's SHA-1 hash.
- * @throws NoSuchAlgorithmException If the SHA-1 algorithm is not
- * available.
*/
- public TrackedTorrent(byte[] torrent)
- throws IOException, NoSuchAlgorithmException {
+ public TrackedTorrent(byte[] torrent) throws IOException {
super(torrent, false);
this.peers = new ConcurrentHashMap();
@@ -89,8 +85,7 @@ public TrackedTorrent(byte[] torrent)
this.announceInterval = TrackedTorrent.DEFAULT_ANNOUNCE_INTERVAL_SECONDS;
}
- public TrackedTorrent(Torrent torrent)
- throws IOException, NoSuchAlgorithmException {
+ public TrackedTorrent(Torrent torrent) throws IOException {
this(torrent.getEncoded());
}
@@ -293,20 +288,9 @@ public List getSomePeers(TrackedPeer peer) {
* @param torrent The abstract {@link File} object representing the
* .torrent file to load.
* @throws IOException When the torrent file cannot be read.
- * @throws NoSuchAlgorithmException
*/
- public static TrackedTorrent load(File torrent) throws IOException,
- NoSuchAlgorithmException {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(torrent);
- byte[] data = new byte[(int)torrent.length()];
- fis.read(data);
- return new TrackedTorrent(data);
- } finally {
- if (fis != null) {
- fis.close();
- }
- }
+ public static TrackedTorrent load(File torrent) throws IOException {
+ byte[] data = FileUtils.readFileToByteArray(torrent);
+ return new TrackedTorrent(data);
}
}
diff --git a/src/main/java/com/turn/ttorrent/tracker/Tracker.java b/src/main/java/com/turn/ttorrent/tracker/Tracker.java
index 6ba05d8b1..638fae259 100644
--- a/src/main/java/com/turn/ttorrent/tracker/Tracker.java
+++ b/src/main/java/com/turn/ttorrent/tracker/Tracker.java
@@ -17,30 +17,21 @@
import com.turn.ttorrent.common.Torrent;
-import java.io.File;
-import java.io.FilenameFilter;
import java.io.IOException;
-import java.io.PrintStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Collection;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import jargs.gnu.CmdLineParser;
-
-import org.apache.log4j.BasicConfigurator;
-import org.apache.log4j.ConsoleAppender;
-import org.apache.log4j.PatternLayout;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import org.simpleframework.transport.connect.Connection;
import org.simpleframework.transport.connect.SocketConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* BitTorrent tracker.
@@ -180,6 +171,13 @@ public void stop() {
}
}
+ /**
+ * Returns the list of tracker's torrents
+ */
+ public Collection getTrackedTorrents() {
+ return torrents.values();
+ }
+
/**
* Announce a new torrent on this tracker.
*
@@ -318,82 +316,4 @@ public void run() {
}
}
}
-
- /**
- * Display program usage on the given {@link PrintStream}.
- */
- private static void usage(PrintStream s) {
- s.println("usage: Tracker [options] [directory]");
- s.println();
- s.println("Available options:");
- s.println(" -h,--help Show this help and exit.");
- s.println(" -p,--port PORT Bind to port PORT.");
- s.println();
- }
-
- /**
- * Main function to start a tracker.
- */
- public static void main(String[] args) {
- BasicConfigurator.configure(new ConsoleAppender(
- new PatternLayout("%d [%-25t] %-5p: %m%n")));
-
- CmdLineParser parser = new CmdLineParser();
- CmdLineParser.Option help = parser.addBooleanOption('h', "help");
- CmdLineParser.Option port = parser.addIntegerOption('p', "port");
-
- try {
- parser.parse(args);
- } catch (CmdLineParser.OptionException oe) {
- System.err.println(oe.getMessage());
- usage(System.err);
- System.exit(1);
- }
-
- // Display help and exit if requested
- if (Boolean.TRUE.equals((Boolean)parser.getOptionValue(help))) {
- usage(System.out);
- System.exit(0);
- }
-
- Integer portValue = (Integer)parser.getOptionValue(port,
- Integer.valueOf(DEFAULT_TRACKER_PORT));
-
- String[] otherArgs = parser.getRemainingArgs();
-
- if (otherArgs.length > 1) {
- usage(System.err);
- System.exit(1);
- }
-
- // Get directory from command-line argument or default to current
- // directory
- String directory = otherArgs.length > 0
- ? otherArgs[0]
- : ".";
-
- FilenameFilter filter = new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return name.endsWith(".torrent");
- }
- };
-
- try {
- Tracker t = new Tracker(new InetSocketAddress(portValue.intValue()));
-
- File parent = new File(directory);
- for (File f : parent.listFiles(filter)) {
- logger.info("Loading torrent from " + f.getName());
- t.announce(TrackedTorrent.load(f));
- }
-
- logger.info("Starting tracker with {} announced torrents...",
- t.torrents.size());
- t.start();
- } catch (Exception e) {
- logger.error("{}", e.getMessage(), e);
- System.exit(2);
- }
- }
}
diff --git a/src/main/java/com/turn/ttorrent/tracker/TrackerService.java b/src/main/java/com/turn/ttorrent/tracker/TrackerService.java
index 014ec5da6..ae24cabd3 100644
--- a/src/main/java/com/turn/ttorrent/tracker/TrackerService.java
+++ b/src/main/java/com/turn/ttorrent/tracker/TrackerService.java
@@ -31,6 +31,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
+import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -117,13 +118,7 @@ public void handle(Request request, Response response) {
} catch (IOException ioe) {
logger.warn("Error while writing response: {}!", ioe.getMessage());
} finally {
- if (body != null) {
- try {
- body.close();
- } catch (IOException ioe) {
- // Ignore
- }
- }
+ IOUtils.closeQuietly(body);
}
}
@@ -252,7 +247,7 @@ private void process(Request request, Response response,
* Tracker HTTP protocol.
*
*
- * @param uri The request's full URI, including query parameters.
+ * @param request The request's full URI, including query parameters.
* @return The {@link AnnounceRequestMessage} representing the client's
* announce request.
*/
@@ -350,7 +345,7 @@ private void serveError(Response response, OutputStream body,
* @param response The HTTP response object.
* @param body The response output stream to write to.
* @param status The HTTP status code to return.
- * @param error The failure reason reported by the tracker.
+ * @param reason The failure reason reported by the tracker.
*/
private void serveError(Response response, OutputStream body,
Status status, ErrorMessage.FailureReason reason) throws IOException {