tracker
Change-Id: I8f8ac81f9c4d7c7650cd64d2dade701dc6c11dce
diff --git a/ttorrent-master/tests/pom.xml b/ttorrent-master/tests/pom.xml
new file mode 100644
index 0000000..d9fecac
--- /dev/null
+++ b/ttorrent-master/tests/pom.xml
@@ -0,0 +1,40 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <name>ttorrent/common</name>
+ <url>http://turn.github.com/ttorrent/</url>
+ <artifactId>ttorrent-tests</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-tracker</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-client</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-test-api</artifactId>
+ <version>1.0</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/ttorrent-master/tests/src/test/java/com/turn/ttorrent/CommunicationManagerFactory.java b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/CommunicationManagerFactory.java
new file mode 100644
index 0000000..ed4be29
--- /dev/null
+++ b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/CommunicationManagerFactory.java
@@ -0,0 +1,51 @@
+package com.turn.ttorrent;
+
+import com.turn.ttorrent.client.CommunicationManager;
+import com.turn.ttorrent.common.LoggerUtils;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class CommunicationManagerFactory {
+
+ public final static int DEFAULT_POOL_SIZE = 10;
+
+ public CommunicationManager getClient(String name) {
+ final ExecutorService executorService = new ThreadPoolExecutor(
+ DEFAULT_POOL_SIZE, DEFAULT_POOL_SIZE,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<Runnable>(4000));
+ final ExecutorService pieceValidatorExecutor = new ThreadPoolExecutor(
+ DEFAULT_POOL_SIZE, DEFAULT_POOL_SIZE,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<Runnable>(400));
+ return new CommunicationManager(executorService, pieceValidatorExecutor) {
+ @Override
+ public void stop() {
+ super.stop();
+
+ int timeout = 60;
+ TimeUnit timeUnit = TimeUnit.SECONDS;
+
+ executorService.shutdown();
+ pieceValidatorExecutor.shutdown();
+ if (timeout > 0) {
+ try {
+ if (!pieceValidatorExecutor.awaitTermination(timeout, timeUnit)) {
+ logger.warn("unable to terminate executor service in {} {}", timeout, timeUnit);
+ }
+ boolean shutdownCorrectly = executorService.awaitTermination(timeout, timeUnit);
+ if (!shutdownCorrectly) {
+ logger.warn("unable to terminate executor service in {} {}", timeout, timeUnit);
+ }
+ } catch (InterruptedException e) {
+ LoggerUtils.warnAndDebugDetails(logger, "unable to await termination executor service, thread was interrupted", e);
+ }
+ }
+
+ }
+ };
+ }
+}
diff --git a/ttorrent-master/tests/src/test/java/com/turn/ttorrent/DummyPeerActivityListener.java b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/DummyPeerActivityListener.java
new file mode 100644
index 0000000..d8a08b7
--- /dev/null
+++ b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/DummyPeerActivityListener.java
@@ -0,0 +1,62 @@
+package com.turn.ttorrent;
+
+import com.turn.ttorrent.client.Piece;
+import com.turn.ttorrent.client.peer.PeerActivityListener;
+import com.turn.ttorrent.client.peer.SharingPeer;
+
+import java.io.IOException;
+import java.util.BitSet;
+
+public class DummyPeerActivityListener implements PeerActivityListener {
+
+
+ @Override
+ public void handlePeerChoked(SharingPeer peer) {
+
+ }
+
+ @Override
+ public void handlePeerReady(SharingPeer peer) {
+
+ }
+
+ @Override
+ public void afterPeerRemoved(SharingPeer peer) {
+
+ }
+
+ @Override
+ public void handlePieceAvailability(SharingPeer peer, Piece piece) {
+
+ }
+
+ @Override
+ public void handleBitfieldAvailability(SharingPeer peer, BitSet availablePieces) {
+
+ }
+
+ @Override
+ public void handlePieceSent(SharingPeer peer, Piece piece) {
+
+ }
+
+ @Override
+ public void handlePieceCompleted(SharingPeer peer, Piece piece) throws IOException {
+
+ }
+
+ @Override
+ public void handlePeerDisconnected(SharingPeer peer) {
+
+ }
+
+ @Override
+ public void handleIOException(SharingPeer peer, IOException ioe) {
+
+ }
+
+ @Override
+ public void handleNewPeerConnected(SharingPeer peer) {
+
+ }
+}
diff --git a/ttorrent-master/tests/src/test/java/com/turn/ttorrent/client/CommunicationManagerTest.java b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/client/CommunicationManagerTest.java
new file mode 100644
index 0000000..7cbc25b
--- /dev/null
+++ b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/client/CommunicationManagerTest.java
@@ -0,0 +1,1508 @@
+package com.turn.ttorrent.client;
+
+import com.turn.ttorrent.*;
+import com.turn.ttorrent.client.peer.SharingPeer;
+import com.turn.ttorrent.client.storage.EmptyPieceStorageFactory;
+import com.turn.ttorrent.client.storage.FileStorage;
+import com.turn.ttorrent.client.storage.FullyPieceStorageFactory;
+import com.turn.ttorrent.client.storage.PieceStorage;
+import com.turn.ttorrent.common.*;
+import com.turn.ttorrent.common.protocol.PeerMessage;
+import com.turn.ttorrent.network.FirstAvailableChannel;
+import com.turn.ttorrent.network.ServerChannelRegister;
+import com.turn.ttorrent.tracker.TrackedPeer;
+import com.turn.ttorrent.tracker.TrackedTorrent;
+import com.turn.ttorrent.tracker.Tracker;
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.*;
+import java.net.*;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.ClosedByInterruptException;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.CRC32;
+import java.util.zip.Checksum;
+
+import static com.turn.ttorrent.CommunicationManagerFactory.DEFAULT_POOL_SIZE;
+import static com.turn.ttorrent.tracker.Tracker.ANNOUNCE_URL;
+import static org.testng.Assert.*;
+
+/**
+ * @author Sergey.Pak
+ * Date: 7/26/13
+ * Time: 2:32 PM
+ */
+@Test(timeOut = 600000)
+public class CommunicationManagerTest {
+
+ private CommunicationManagerFactory communicationManagerFactory;
+
+ private List<CommunicationManager> communicationManagerList;
+ private static final String TEST_RESOURCES = "src/test/resources";
+ private Tracker tracker;
+ private TempFiles tempFiles;
+
+ public CommunicationManagerTest() {
+ communicationManagerFactory = new CommunicationManagerFactory();
+ if (Logger.getRootLogger().getAllAppenders().hasMoreElements())
+ return;
+ BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("[%d{MMdd HH:mm:ss,SSS} %t] %6p - %20.20c - %m %n")));
+ }
+
+ @BeforeMethod
+ public void setUp() throws IOException {
+ tempFiles = new TempFiles();
+ communicationManagerList = new ArrayList<CommunicationManager>();
+ Logger.getRootLogger().setLevel(Utils.getLogLevel());
+ startTracker();
+ }
+
+ private void saveTorrent(TorrentMetadata torrent, File file) throws IOException {
+ FileOutputStream fos = new FileOutputStream(file);
+ fos.write(new TorrentSerializer().serialize(torrent));
+ fos.close();
+ }
+
+ public void testThatSeederIsNotReceivedHaveMessages() throws Exception {
+ final ExecutorService workerES = Executors.newFixedThreadPool(10);
+ final ExecutorService validatorES = Executors.newFixedThreadPool(4);
+ final AtomicBoolean isSeederReceivedHaveMessage = new AtomicBoolean(false);
+ CommunicationManager seeder = new CommunicationManager(workerES, validatorES) {
+
+ @Override
+ public SharingPeer createSharingPeer(String host, int port, ByteBuffer peerId, SharedTorrent torrent, ByteChannel channel, String clientIdentifier, int clientVersion) {
+ return new SharingPeer(host, port, peerId, torrent, getConnectionManager(), this, channel, "TO", 1234) {
+ @Override
+ public synchronized void handleMessage(PeerMessage msg) {
+ if (msg instanceof PeerMessage.HaveMessage) {
+ isSeederReceivedHaveMessage.set(true);
+ }
+ super.handleMessage(msg);
+ }
+ };
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ workerES.shutdown();
+ validatorES.shutdown();
+ }
+ };
+ communicationManagerList.add(seeder);
+
+ File tempFile = tempFiles.createTempFile(100 * 1025 * 1024);
+ URL announce = new URL("http://127.0.0.1:6969/announce");
+ URI announceURI = announce.toURI();
+
+ TorrentMetadata torrent = TorrentCreator.create(tempFile, announceURI, "Test");
+ File torrentFile = new File(tempFile.getParentFile(), tempFile.getName() + ".torrent");
+ saveTorrent(torrent, torrentFile);
+
+ seeder.addTorrent(torrentFile.getAbsolutePath(), tempFile.getParent());
+ seeder.start(InetAddress.getLocalHost());
+
+ waitForSeederIsAnnounsedOnTracker(torrent.getHexInfoHash());
+
+ CommunicationManager leecher = createClient();
+ leecher.start(InetAddress.getLocalHost());
+ TorrentManager torrentManager = leecher.addTorrent(torrentFile.getAbsolutePath(), tempFiles.createTempDir().getAbsolutePath());
+ waitDownloadComplete(torrentManager, 15);
+ assertFalse(isSeederReceivedHaveMessage.get());
+ }
+
+ private void waitDownloadComplete(TorrentManager torrentManager, int timeoutSec) throws InterruptedException {
+ final Semaphore semaphore = new Semaphore(0);
+ TorrentListenerWrapper listener = new TorrentListenerWrapper() {
+ @Override
+ public void downloadComplete() {
+ semaphore.release();
+ }
+ };
+ try {
+ torrentManager.addListener(listener);
+ boolean res = semaphore.tryAcquire(timeoutSec, TimeUnit.SECONDS);
+ if (!res) throw new RuntimeException("Unable to download file in " + timeoutSec + " seconds");
+ } finally {
+ torrentManager.removeListener(listener);
+ }
+ }
+
+ private void waitForSeederIsAnnounsedOnTracker(final String hexInfoHash) {
+ final WaitFor waitFor = new WaitFor(10 * 1000) {
+ @Override
+ protected boolean condition() {
+ return tracker.getTrackedTorrent(hexInfoHash) != null;
+ }
+ };
+ assertTrue(waitFor.isMyResult());
+ }
+
+
+ // @Test(invocationCount = 50)
+ public void download_multiple_files() throws IOException, InterruptedException, URISyntaxException {
+ int numFiles = 50;
+ this.tracker.setAcceptForeignTorrents(true);
+
+ final File srcDir = tempFiles.createTempDir();
+ final File downloadDir = tempFiles.createTempDir();
+
+ CommunicationManager seeder = createClient("seeder");
+ seeder.start(InetAddress.getLocalHost());
+ CommunicationManager leech = null;
+
+
+ try {
+ URL announce = new URL("http://127.0.0.1:6969/announce");
+ URI announceURI = announce.toURI();
+ final Set<String> names = new HashSet<String>();
+ List<File> filesToShare = new ArrayList<File>();
+ for (int i = 0; i < numFiles; i++) {
+ File tempFile = tempFiles.createTempFile(513 * 1024);
+ File srcFile = new File(srcDir, tempFile.getName());
+ assertTrue(tempFile.renameTo(srcFile));
+
+ TorrentMetadata torrent = TorrentCreator.create(srcFile, announceURI, "Test");
+ File torrentFile = new File(srcFile.getParentFile(), srcFile.getName() + ".torrent");
+ saveTorrent(torrent, torrentFile);
+ filesToShare.add(srcFile);
+ names.add(srcFile.getName());
+ }
+
+ for (File f : filesToShare) {
+ File torrentFile = new File(f.getParentFile(), f.getName() + ".torrent");
+ seeder.addTorrent(torrentFile.getAbsolutePath(), f.getParent());
+ }
+ leech = createClient("leecher");
+ leech.start(new InetAddress[]{InetAddress.getLocalHost()}, 5, null, new SelectorFactoryImpl());
+ for (File f : filesToShare) {
+ File torrentFile = new File(f.getParentFile(), f.getName() + ".torrent");
+ leech.addTorrent(torrentFile.getAbsolutePath(), downloadDir.getAbsolutePath());
+ }
+
+ new WaitFor(60 * 1000) {
+ @Override
+ protected boolean condition() {
+
+ final Set<String> strings = listFileNames(downloadDir);
+ int count = 0;
+ final List<String> partItems = new ArrayList<String>();
+ for (String s : strings) {
+ if (s.endsWith(".part")) {
+ count++;
+ partItems.add(s);
+ }
+ }
+ if (count < 5) {
+
+ System.err.printf("Count: %d. Items: %s%n", count, Arrays.toString(partItems.toArray()));
+ }
+ return strings.containsAll(names);
+ }
+ };
+
+ assertEquals(listFileNames(downloadDir), names);
+ } finally {
+ leech.stop();
+ seeder.stop();
+ }
+ }
+
+ private Set<String> listFileNames(File downloadDir) {
+ if (downloadDir == null) return Collections.emptySet();
+ Set<String> names = new HashSet<String>();
+ File[] files = downloadDir.listFiles();
+ if (files == null) return Collections.emptySet();
+ for (File f : files) {
+ names.add(f.getName());
+ }
+ return names;
+ }
+
+ public void testHungSeeder() throws Exception {
+ this.tracker.setAcceptForeignTorrents(true);
+
+ File tempFile = tempFiles.createTempFile(500 * 1025 * 1024);
+ URL announce = new URL("http://127.0.0.1:6969/announce");
+ URI announceURI = announce.toURI();
+
+ TorrentMetadata torrent = TorrentCreator.create(tempFile, announceURI, "Test");
+ File torrentFile = new File(tempFile.getParentFile(), tempFile.getName() + ".torrent");
+ saveTorrent(torrent, torrentFile);
+
+ CommunicationManager goodSeeder = createClient();
+ goodSeeder.addTorrent(torrentFile.getAbsolutePath(), tempFile.getParent(), FullyPieceStorageFactory.INSTANCE);
+
+ final ExecutorService es = Executors.newFixedThreadPool(10);
+ final ExecutorService validatorES = Executors.newFixedThreadPool(4);
+ CommunicationManager hungSeeder = new CommunicationManager(es, validatorES) {
+ @Override
+ public void stop() {
+ super.stop();
+ es.shutdownNow();
+ validatorES.shutdownNow();
+ }
+
+ @Override
+ public SharingPeer createSharingPeer(String host, int port, ByteBuffer peerId, SharedTorrent torrent, ByteChannel channel, String clientIdentifier, int clientVersion) {
+ return new SharingPeer(host, port, peerId, torrent, getConnectionManager(), this, channel, clientIdentifier, clientVersion) {
+ @Override
+ public void handleMessage(PeerMessage msg) {
+ if (msg instanceof PeerMessage.RequestMessage) {
+ return;
+ }
+ super.handleMessage(msg);
+ }
+ };
+ }
+ };
+ hungSeeder.addTorrent(torrentFile.getAbsolutePath(), tempFile.getParent(), FullyPieceStorageFactory.INSTANCE);
+
+ final File downloadDir = tempFiles.createTempDir();
+ CommunicationManager leech = createClient();
+ leech.addTorrent(torrentFile.getAbsolutePath(), downloadDir.getAbsolutePath(), EmptyPieceStorageFactory.INSTANCE);
+
+ try {
+ hungSeeder.start(InetAddress.getLocalHost());
+ goodSeeder.start(InetAddress.getLocalHost());
+ leech.start(InetAddress.getLocalHost());
+
+ waitForFileInDir(downloadDir, tempFile.getName());
+ assertFilesEqual(tempFile, new File(downloadDir, tempFile.getName()));
+ } finally {
+ goodSeeder.stop();
+ leech.stop();
+ }
+ }
+
+ public void large_file_download() throws IOException, URISyntaxException, InterruptedException {
+ this.tracker.setAcceptForeignTorrents(true);
+
+ File tempFile = tempFiles.createTempFile(201 * 1025 * 1024);
+ URL announce = new URL("http://127.0.0.1:6969/announce");
+ URI announceURI = announce.toURI();
+
+ TorrentMetadata torrent = TorrentCreator.create(tempFile, announceURI, "Test");
+ File torrentFile = new File(tempFile.getParentFile(), tempFile.getName() + ".torrent");
+ saveTorrent(torrent, torrentFile);
+
+ CommunicationManager seeder = createClient();
+ seeder.addTorrent(torrentFile.getAbsolutePath(), tempFile.getParent(), FullyPieceStorageFactory.INSTANCE);
+
+ final File downloadDir = tempFiles.createTempDir();
+ CommunicationManager leech = createClient();
+ leech.addTorrent(torrentFile.getAbsolutePath(), downloadDir.getAbsolutePath(), EmptyPieceStorageFactory.INSTANCE);
+
+ try {
+ seeder.start(InetAddress.getLocalHost());
+ leech.start(InetAddress.getLocalHost());
+
+ waitForFileInDir(downloadDir, tempFile.getName());
+ assertFilesEqual(tempFile, new File(downloadDir, tempFile.getName()));
+ } finally {
+ seeder.stop();
+ leech.stop();
+ }
+ }
+
+ // TODO: 24.09.2018 flaky test, it's needed to debug and fix
+ @Test(enabled = false)
+ public void testManyLeechers() throws IOException, URISyntaxException, InterruptedException {
+ this.tracker.setAcceptForeignTorrents(true);
+
+ File tempFile = tempFiles.createTempFile(400 * 1025 * 1024);
+ URL announce = new URL("http://127.0.0.1:6969/announce");
+ URI announceURI = announce.toURI();
+
+ TorrentMetadata torrent = TorrentCreator.create(tempFile, announceURI, "Test");
+ File torrentFile = new File(tempFile.getParentFile(), tempFile.getName() + ".torrent");
+ saveTorrent(torrent, torrentFile);
+
+ CommunicationManager seeder = createClient();
+ seeder.addTorrent(torrentFile.getAbsolutePath(), tempFile.getParent(), FullyPieceStorageFactory.INSTANCE);
+
+ List<Map.Entry<CommunicationManager, File>> leechers = new ArrayList<Map.Entry<CommunicationManager, File>>();
+ for (int i = 0; i < 4; i++) {
+ final File downloadDir = tempFiles.createTempDir();
+ CommunicationManager leech = createClient();
+ leech.addTorrent(torrentFile.getAbsolutePath(), downloadDir.getAbsolutePath(), EmptyPieceStorageFactory.INSTANCE);
+ leechers.add(new AbstractMap.SimpleEntry<CommunicationManager, File>(leech, downloadDir));
+ }
+
+ try {
+ seeder.start(InetAddress.getLocalHost());
+ for (Map.Entry<CommunicationManager, File> entry : leechers) {
+ entry.getKey().start(InetAddress.getLocalHost());
+ }
+
+ for (Map.Entry<CommunicationManager, File> leecher : leechers) {
+ File downloadDir = leecher.getValue();
+ waitForFileInDir(downloadDir, tempFile.getName());
+ assertFilesEqual(tempFile, new File(downloadDir, tempFile.getName()));
+ }
+ } finally {
+ seeder.stop();
+ for (Map.Entry<CommunicationManager, File> e : leechers) {
+ e.getKey().stop();
+ }
+ }
+ }
+
+ @Test(enabled = false)
+ public void endgameModeTest() throws Exception {
+ this.tracker.setAcceptForeignTorrents(true);
+ final int numSeeders = 2;
+ List<CommunicationManager> seeders = new ArrayList<CommunicationManager>();
+ final AtomicInteger skipPiecesCount = new AtomicInteger(1);
+ for (int i = 0; i < numSeeders; i++) {
+ final ExecutorService es = Executors.newFixedThreadPool(10);
+ final ExecutorService validatorES = Executors.newFixedThreadPool(4);
+ final CommunicationManager seeder = new CommunicationManager(es, validatorES) {
+ @Override
+ public void stop() {
+ super.stop();
+ es.shutdownNow();
+ validatorES.shutdownNow();
+ }
+
+ @Override
+ public SharingPeer createSharingPeer(String host, int port, ByteBuffer peerId, SharedTorrent torrent, ByteChannel channel, String clientIdentifier, int clientVersion) {
+ return new SharingPeer(host, port, peerId, torrent, getConnectionManager(), this, channel, "TO", 1234) {
+ @Override
+ public void send(PeerMessage message) throws IllegalStateException {
+ if (message instanceof PeerMessage.PieceMessage) {
+ if (skipPiecesCount.getAndDecrement() > 0) {
+ return;
+ }
+ }
+ super.send(message);
+ }
+ };
+ }
+ };
+ seeders.add(seeder);
+ communicationManagerList.add(seeder);
+ }
+ File tempFile = tempFiles.createTempFile(1024 * 20 * 1024);
+
+ TorrentMetadata torrent = TorrentCreator.create(tempFile, this.tracker.getAnnounceURI(), "Test");
+ File torrentFile = new File(tempFile.getParentFile(), tempFile.getName() + ".torrent");
+ saveTorrent(torrent, torrentFile);
+
+ for (int i = 0; i < numSeeders; i++) {
+ CommunicationManager communicationManager = seeders.get(i);
+ communicationManager.addTorrent(torrentFile.getAbsolutePath(), tempFile.getParent());
+ communicationManager.start(InetAddress.getLocalHost());
+ }
+
+ final File downloadDir = tempFiles.createTempDir();
+ CommunicationManager leech = createClient();
+ leech.start(InetAddress.getLocalHost());
+ TorrentManager torrentManager = leech.addTorrent(torrentFile.getAbsolutePath(), downloadDir.getParent());
+ waitDownloadComplete(torrentManager, 20);
+
+ waitForFileInDir(downloadDir, tempFile.getName());
+
+ }
+
+
+ public void more_than_one_seeder_for_same_torrent() throws IOException, InterruptedException {
+ this.tracker.setAcceptForeignTorrents(true);
+ assertEquals(0, this.tracker.getTrackedTorrents().size());
+
+ final int numSeeders = 5;
+ List<CommunicationManager> seeders = new ArrayList<CommunicationManager>();
+ for (int i = 0; i < numSeeders; i++) {
+ seeders.add(createClient());
+ }
+
+ try {
+ File tempFile = tempFiles.createTempFile(100 * 1024);
+
+ TorrentMetadata torrent = TorrentCreator.create(tempFile, this.tracker.getAnnounceURI(), "Test");
+ File torrentFile = new File(tempFile.getParentFile(), tempFile.getName() + ".torrent");
+ saveTorrent(torrent, torrentFile);
+
+ for (int i = 0; i < numSeeders; i++) {
+ CommunicationManager communicationManager = seeders.get(i);
+ communicationManager.addTorrent(torrentFile.getAbsolutePath(), tempFile.getParent());
+ communicationManager.start(InetAddress.getLocalHost());
+ }
+
+ new WaitFor() {
+ @Override
+ protected boolean condition() {
+ for (TrackedTorrent tt : tracker.getTrackedTorrents()) {
+ if (tt.getPeers().size() == numSeeders) return true;
+ }
+
+ return false;
+ }
+ };
+
+ Collection<TrackedTorrent> torrents = this.tracker.getTrackedTorrents();
+ assertEquals(torrents.size(), 1);
+ assertEquals(numSeeders, torrents.iterator().next().seeders());
+ } finally {
+ for (CommunicationManager communicationManager : seeders) {
+ communicationManager.stop();
+ }
+ }
+
+ }
+
+ public void testThatDownloadStatisticProvidedToTracker() throws Exception {
+ final ExecutorService executorService = Executors.newFixedThreadPool(8);
+ final ExecutorService validatorES = Executors.newFixedThreadPool(4);
+ final AtomicInteger countOfTrackerResponses = new AtomicInteger(0);
+ CommunicationManager leecher = new CommunicationManager(executorService, validatorES) {
+ @Override
+ public void handleDiscoveredPeers(List<Peer> peers, String hexInfoHash) {
+ super.handleDiscoveredPeers(peers, hexInfoHash);
+ countOfTrackerResponses.incrementAndGet();
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ executorService.shutdownNow();
+ validatorES.shutdownNow();
+ }
+ };
+
+ communicationManagerList.add(leecher);
+
+ final int fileSize = 2 * 1025 * 1024;
+ File tempFile = tempFiles.createTempFile(fileSize);
+ URL announce = new URL("http://127.0.0.1:6969/announce");
+ URI announceURI = announce.toURI();
+
+ TorrentMetadata torrent = TorrentCreator.create(tempFile, announceURI, "Test");
+ File torrentFile = new File(tempFile.getParentFile(), tempFile.getName() + ".torrent");
+ saveTorrent(torrent, torrentFile);
+
+ String hash = leecher.addTorrent(torrentFile.getAbsolutePath(), tempFiles.createTempDir().getAbsolutePath()).getHexInfoHash();
+ leecher.start(InetAddress.getLocalHost());
+ final LoadedTorrent announceableTorrent = leecher.getTorrentsStorage().getLoadedTorrent(hash);
+
+ final SharedTorrent sharedTorrent = leecher.getTorrentsStorage().putIfAbsentActiveTorrent(announceableTorrent.getTorrentHash().getHexInfoHash(),
+ leecher.getTorrentLoader().loadTorrent(announceableTorrent));
+
+ sharedTorrent.init();
+
+ new WaitFor(10 * 1000) {
+ @Override
+ protected boolean condition() {
+ return countOfTrackerResponses.get() == 1;
+ }
+ };
+
+ final TrackedTorrent trackedTorrent = tracker.getTrackedTorrent(announceableTorrent.getTorrentHash().getHexInfoHash());
+
+ assertEquals(trackedTorrent.getPeers().size(), 1);
+
+ final TrackedPeer trackedPeer = trackedTorrent.getPeers().values().iterator().next();
+
+ assertEquals(trackedPeer.getUploaded(), 0);
+ assertEquals(trackedPeer.getDownloaded(), 0);
+ assertEquals(trackedPeer.getLeft(), fileSize);
+
+ Piece piece = sharedTorrent.getPiece(1);
+ sharedTorrent.handlePieceCompleted(null, piece);
+ sharedTorrent.markCompleted(piece);
+
+ new WaitFor(10 * 1000) {
+ @Override
+ protected boolean condition() {
+ return countOfTrackerResponses.get() >= 2;
+ }
+ };
+ int downloaded = 512 * 1024;//one piece
+ assertEquals(trackedPeer.getUploaded(), 0);
+ assertEquals(trackedPeer.getDownloaded(), downloaded);
+ assertEquals(trackedPeer.getLeft(), fileSize - downloaded);
+ }
+
+ public void no_full_seeder_test() throws IOException, URISyntaxException, InterruptedException, NoSuchAlgorithmException {
+ this.tracker.setAcceptForeignTorrents(true);
+
+ final int pieceSize = 48 * 1024; // lower piece size to reduce disk usage
+ final int numSeeders = 6;
+ final int piecesCount = numSeeders * 3 + 15;
+
+ final List<CommunicationManager> clientsList;
+ clientsList = new ArrayList<CommunicationManager>(piecesCount);
+
+ final MessageDigest md5 = MessageDigest.getInstance("MD5");
+
+ try {
+ File tempFile = tempFiles.createTempFile(piecesCount * pieceSize);
+ List<File> targetFiles = new ArrayList<File>();
+
+ String hash = createMultipleSeedersWithDifferentPieces(tempFile, piecesCount, pieceSize, numSeeders, clientsList, targetFiles);
+ String baseMD5 = getFileMD5(tempFile, md5);
+ assertEquals(numSeeders, targetFiles.size());
+
+ validateMultipleClientsResults(clientsList, md5, tempFile, baseMD5, hash, targetFiles);
+
+ } finally {
+ for (CommunicationManager communicationManager : clientsList) {
+ communicationManager.stop();
+ }
+ }
+ }
+
+ @Test(enabled = false)
+ public void corrupted_seeder_repair() throws IOException, URISyntaxException, InterruptedException, NoSuchAlgorithmException {
+ this.tracker.setAcceptForeignTorrents(true);
+
+ final int pieceSize = 48 * 1024; // lower piece size to reduce disk usage
+ final int numSeeders = 6;
+ final int piecesCount = numSeeders + 7;
+
+ final List<CommunicationManager> clientsList;
+ clientsList = new ArrayList<CommunicationManager>(piecesCount);
+
+ final MessageDigest md5 = MessageDigest.getInstance("MD5");
+
+ try {
+ File baseFile = tempFiles.createTempFile(piecesCount * pieceSize);
+ List<File> targetFiles = new ArrayList<File>();
+
+ String hash = createMultipleSeedersWithDifferentPieces(baseFile, piecesCount, pieceSize, numSeeders, clientsList, targetFiles);
+ assertEquals(numSeeders, targetFiles.size());
+ String baseMD5 = getFileMD5(baseFile, md5);
+ final CommunicationManager firstCommunicationManager = clientsList.get(0);
+
+ new WaitFor(10 * 1000) {
+ @Override
+ protected boolean condition() {
+ return firstCommunicationManager.getTorrentsStorage().activeTorrents().size() >= 1;
+ }
+ };
+
+ final SharedTorrent torrent = firstCommunicationManager.getTorrents().iterator().next();
+ final File file = new File(targetFiles.get(0).getParentFile(), TorrentUtils.getTorrentFileNames(torrent).get(0));
+ final int oldByte;
+ {
+ RandomAccessFile raf = new RandomAccessFile(file, "rw");
+ raf.seek(0);
+ oldByte = raf.read();
+ raf.seek(0);
+ // replacing the byte
+ if (oldByte != 35) {
+ raf.write(35);
+ } else {
+ raf.write(45);
+ }
+ raf.close();
+ }
+ final WaitFor waitFor = new WaitFor(60 * 1000) {
+ @Override
+ protected boolean condition() {
+ for (CommunicationManager client : clientsList) {
+ final SharedTorrent next = client.getTorrents().iterator().next();
+ if (next.getCompletedPieces().cardinality() < next.getPieceCount() - 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+ };
+
+ if (!waitFor.isMyResult()) {
+ fail("All seeders didn't get their files");
+ }
+ Thread.sleep(10 * 1000);
+ {
+ byte[] piece = new byte[pieceSize];
+ FileInputStream fin = new FileInputStream(baseFile);
+ fin.read(piece);
+ fin.close();
+ RandomAccessFile raf;
+ try {
+ raf = new RandomAccessFile(file, "rw");
+ raf.seek(0);
+ raf.write(oldByte);
+ raf.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ validateMultipleClientsResults(clientsList, md5, baseFile, baseMD5, hash, targetFiles);
+
+ } finally {
+ for (CommunicationManager communicationManager : clientsList) {
+ communicationManager.stop();
+ }
+ }
+ }
+
+ public void testThatTorrentsHaveLazyInitAndRemovingAfterDownload()
+ throws IOException, InterruptedException, URISyntaxException {
+ final CommunicationManager seeder = createClient();
+ File tempFile = tempFiles.createTempFile(100 * 1025 * 1024);
+ URL announce = new URL("http://127.0.0.1:6969/announce");
+ URI announceURI = announce.toURI();
+
+ TorrentMetadata torrent = TorrentCreator.create(tempFile, announceURI, "Test");
+ File torrentFile = new File(tempFile.getParentFile(), tempFile.getName() + ".torrent");
+ saveTorrent(torrent, torrentFile);
+ seeder.addTorrent(torrentFile.getAbsolutePath(), tempFile.getParentFile().getAbsolutePath());
+
+ final CommunicationManager leecher = createClient();
+ File downloadDir = tempFiles.createTempDir();
+ leecher.addTorrent(torrentFile.getAbsolutePath(), downloadDir.getAbsolutePath());
+ seeder.start(InetAddress.getLocalHost());
+
+ assertEquals(1, seeder.getTorrentsStorage().announceableTorrents().size());
+ assertEquals(0, seeder.getTorrentsStorage().activeTorrents().size());
+ assertEquals(0, leecher.getTorrentsStorage().activeTorrents().size());
+
+ leecher.start(InetAddress.getLocalHost());
+
+ WaitFor waitFor = new WaitFor(10 * 1000) {
+
+ @Override
+ protected boolean condition() {
+ return seeder.getTorrentsStorage().activeTorrents().size() == 1 &&
+ leecher.getTorrentsStorage().activeTorrents().size() == 1;
+ }
+ };
+
+ assertTrue(waitFor.isMyResult(), "Torrent was not successfully initialized");
+
+ assertEquals(1, seeder.getTorrentsStorage().activeTorrents().size());
+ assertEquals(1, leecher.getTorrentsStorage().activeTorrents().size());
+
+ waitForFileInDir(downloadDir, tempFile.getName());
+ assertFilesEqual(tempFile, new File(downloadDir, tempFile.getName()));
+
+ waitFor = new WaitFor(10 * 1000) {
+
+ @Override
+ protected boolean condition() {
+ return seeder.getTorrentsStorage().activeTorrents().size() == 0 &&
+ leecher.getTorrentsStorage().activeTorrents().size() == 0;
+ }
+ };
+
+ assertTrue(waitFor.isMyResult(), "Torrent was not successfully removed");
+
+ assertEquals(0, seeder.getTorrentsStorage().activeTorrents().size());
+ assertEquals(0, leecher.getTorrentsStorage().activeTorrents().size());
+
+ }
+
+ public void corrupted_seeder() throws IOException, InterruptedException, NoSuchAlgorithmException {
+ this.tracker.setAcceptForeignTorrents(true);
+
+ final int pieceSize = 48 * 1024; // lower piece size to reduce disk usage
+ final int piecesCount = 35;
+
+ final List<CommunicationManager> clientsList;
+ clientsList = new ArrayList<CommunicationManager>(piecesCount);
+
+ final MessageDigest md5 = MessageDigest.getInstance("MD5");
+
+ try {
+ final File baseFile = tempFiles.createTempFile(piecesCount * pieceSize);
+ final File badFile = tempFiles.createTempFile(piecesCount * pieceSize);
+
+ final CommunicationManager communicationManager2 = createAndStartClient();
+ final File client2Dir = tempFiles.createTempDir();
+ final File client2File = new File(client2Dir, baseFile.getName());
+ FileUtils.copyFile(badFile, client2File);
+
+ final TorrentMetadata torrent = TorrentCreator.create(baseFile, null, this.tracker.getAnnounceURI(), null, "Test", pieceSize);
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+
+ communicationManager2.addTorrent(torrentFile.getAbsolutePath(), client2Dir.getAbsolutePath());
+
+ final CommunicationManager leech = createAndStartClient();
+ final File leechDestDir = tempFiles.createTempDir();
+ final AtomicReference<Exception> thrownException = new AtomicReference<Exception>();
+ final Thread th = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ TorrentManager torrentManager = leech.addTorrent(torrentFile.getAbsolutePath(), leechDestDir.getAbsolutePath());
+ waitDownloadComplete(torrentManager, 10);
+ } catch (Exception e) {
+ thrownException.set(e);
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ th.start();
+ final WaitFor waitFor = new WaitFor(30 * 1000) {
+ @Override
+ protected boolean condition() {
+ return th.getState() == Thread.State.TERMINATED;
+ }
+ };
+
+ final Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
+ for (Map.Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet()) {
+ System.out.printf("%s:%n", entry.getKey().getName());
+ for (StackTraceElement elem : entry.getValue()) {
+ System.out.println(elem.toString());
+ }
+ }
+
+ assertTrue(waitFor.isMyResult());
+ assertNotNull(thrownException.get());
+ assertTrue(thrownException.get().getMessage().contains("Unable to download"));
+
+ } finally {
+ for (CommunicationManager communicationManager : clientsList) {
+ communicationManager.stop();
+ }
+ }
+ }
+
+ public void unlock_file_when_no_leechers() throws InterruptedException, IOException {
+ CommunicationManager seeder = createClient();
+ tracker.setAcceptForeignTorrents(true);
+
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 7);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+
+ seeder.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ seeder.start(InetAddress.getLocalHost());
+
+ downloadAndStop(torrent, 15 * 1000, createClient());
+ Thread.sleep(2 * 1000);
+ assertTrue(dwnlFile.exists() && dwnlFile.isFile());
+ final boolean delete = dwnlFile.delete();
+ assertTrue(delete && !dwnlFile.exists());
+ }
+
+ public void download_many_times() throws InterruptedException, IOException {
+ CommunicationManager seeder = createClient();
+ tracker.setAcceptForeignTorrents(true);
+
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 7);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+
+ seeder.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ seeder.start(InetAddress.getLocalHost());
+
+ for (int i = 0; i < 5; i++) {
+ downloadAndStop(torrent, 250 * 1000, createClient());
+ Thread.sleep(3 * 1000);
+ }
+ }
+
+ public void testConnectToAllDiscoveredPeers() throws Exception {
+ tracker.setAcceptForeignTorrents(true);
+
+ final ExecutorService executorService = Executors.newFixedThreadPool(8);
+ final ExecutorService validatorES = Executors.newFixedThreadPool(4);
+ CommunicationManager leecher = new CommunicationManager(executorService, validatorES) {
+ @Override
+ public void stop() {
+ super.stop();
+ executorService.shutdownNow();
+ validatorES.shutdownNow();
+ }
+ };
+ leecher.setMaxInConnectionsCount(10);
+ leecher.setMaxOutConnectionsCount(10);
+
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 34);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+
+ final String hexInfoHash = leecher.addTorrent(torrentFile.getAbsolutePath(), tempFiles.createTempDir().getAbsolutePath()).getHexInfoHash();
+ final List<ServerSocket> serverSockets = new ArrayList<ServerSocket>();
+
+ final int startPort = 6885;
+ int port = startPort;
+ PeerUID[] peerUids = new PeerUID[]{
+ new PeerUID(new InetSocketAddress("127.0.0.1", port++), hexInfoHash),
+ new PeerUID(new InetSocketAddress("127.0.0.1", port++), hexInfoHash),
+ new PeerUID(new InetSocketAddress("127.0.0.1", port), hexInfoHash)
+ };
+ final ExecutorService es = Executors.newSingleThreadExecutor();
+ try {
+ leecher.start(InetAddress.getLocalHost());
+
+ WaitFor waitFor = new WaitFor(5000) {
+ @Override
+ protected boolean condition() {
+ return tracker.getTrackedTorrent(hexInfoHash) != null;
+ }
+ };
+
+ assertTrue(waitFor.isMyResult());
+
+ final TrackedTorrent trackedTorrent = tracker.getTrackedTorrent(hexInfoHash);
+ Map<PeerUID, TrackedPeer> trackedPeerMap = new HashMap<PeerUID, TrackedPeer>();
+
+ port = startPort;
+ for (PeerUID uid : peerUids) {
+ trackedPeerMap.put(uid, new TrackedPeer(trackedTorrent, "127.0.0.1", port, ByteBuffer.wrap("id".getBytes(Constants.BYTE_ENCODING))));
+ serverSockets.add(new ServerSocket(port));
+ port++;
+ }
+
+ trackedTorrent.getPeers().putAll(trackedPeerMap);
+
+ //wait until all server sockets accept connection from leecher
+ for (final ServerSocket ss : serverSockets) {
+ final Future<?> future = es.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ final Socket socket = ss.accept();
+ socket.close();
+ } catch (IOException e) {
+ throw new RuntimeException("can not accept connection");
+ }
+ }
+ });
+ try {
+ future.get(10, TimeUnit.SECONDS);
+ } catch (ExecutionException e) {
+ fail("get execution exception on accept connection", e);
+ } catch (TimeoutException e) {
+ fail("not received connection from leecher in specified timeout", e);
+ }
+ }
+
+ } finally {
+ for (ServerSocket ss : serverSockets) {
+ try {
+ ss.close();
+ } catch (IOException e) {
+ fail("can not close server socket", e);
+ }
+ }
+ es.shutdown();
+ leecher.stop();
+ }
+ }
+
+ public void download_io_error() throws InterruptedException, IOException {
+ tracker.setAcceptForeignTorrents(true);
+ CommunicationManager seeder = createClient();
+
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 34);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+
+ seeder.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ seeder.start(InetAddress.getLocalHost());
+
+ final AtomicInteger interrupts = new AtomicInteger(0);
+ final ExecutorService es = Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
+ final ExecutorService validatorES = Executors.newFixedThreadPool(4);
+ final CommunicationManager leech = new CommunicationManager(es, validatorES) {
+ @Override
+ public void handlePieceCompleted(SharingPeer peer, Piece piece) throws IOException {
+ super.handlePieceCompleted(peer, piece);
+ if (piece.getIndex() % 4 == 0 && interrupts.incrementAndGet() <= 2) {
+ peer.unbind(true);
+ }
+ }
+
+ @Override
+ public void stop(int timeout, TimeUnit timeUnit) {
+ super.stop(timeout, timeUnit);
+ es.shutdown();
+ validatorES.shutdown();
+ }
+ };
+ //manually add leech here for graceful shutdown.
+ communicationManagerList.add(leech);
+ downloadAndStop(torrent, 45 * 1000, leech);
+ Thread.sleep(2 * 1000);
+ }
+
+ public void download_uninterruptibly_positive() throws InterruptedException, IOException {
+ tracker.setAcceptForeignTorrents(true);
+ CommunicationManager seeder = createClient();
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 24);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+ seeder.start(InetAddress.getLocalHost());
+ seeder.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ CommunicationManager leecher = createClient();
+ leecher.start(InetAddress.getLocalHost());
+ TorrentManager torrentManager = leecher.addTorrent(torrentFile.getAbsolutePath(), tempFiles.createTempDir().getAbsolutePath());
+ waitDownloadComplete(torrentManager, 10);
+ }
+
+ public void download_uninterruptibly_negative() throws InterruptedException, IOException {
+ tracker.setAcceptForeignTorrents(true);
+ final AtomicInteger downloadedPiecesCount = new AtomicInteger(0);
+ final CommunicationManager seeder = createClient();
+
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 24);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+ seeder.start(InetAddress.getLocalHost());
+ seeder.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ final ExecutorService es = Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
+ final ExecutorService validatorES = Executors.newFixedThreadPool(4);
+ final CommunicationManager leecher = new CommunicationManager(es, validatorES) {
+ @Override
+ public void stop(int timeout, TimeUnit timeUnit) {
+ super.stop(timeout, timeUnit);
+ es.shutdown();
+ validatorES.shutdown();
+ }
+
+ @Override
+ public void handlePieceCompleted(SharingPeer peer, Piece piece) throws IOException {
+ super.handlePieceCompleted(peer, piece);
+ if (downloadedPiecesCount.incrementAndGet() > 10) {
+ seeder.stop();
+ }
+ }
+ };
+ communicationManagerList.add(leecher);
+ leecher.start(InetAddress.getLocalHost());
+ final File destDir = tempFiles.createTempDir();
+ TorrentManager torrentManager = leecher.addTorrent(torrentFile.getAbsolutePath(), destDir.getAbsolutePath());
+ try {
+ waitDownloadComplete(torrentManager, 7);
+ fail("Must fail, because file wasn't downloaded completely");
+ } catch (RuntimeException ex) {
+
+ LoadedTorrent loadedTorrent = leecher.getTorrentsStorage().getLoadedTorrent(torrentManager.getHexInfoHash());
+ loadedTorrent.getPieceStorage().close();
+
+ // delete .part file
+ File[] destDirFiles = destDir.listFiles();
+ assertNotNull(destDirFiles);
+ assertEquals(1, destDirFiles.length);
+ File targetFile = destDirFiles[0];
+ if (!targetFile.delete()) {
+ fail("Unable to remove file " + targetFile);
+ }
+ // ensure .part was deleted:
+ destDirFiles = destDir.listFiles();
+ assertNotNull(destDirFiles);
+ assertEquals(0, destDirFiles.length);
+ }
+
+ }
+
+ public void download_uninterruptibly_timeout() throws InterruptedException, IOException {
+ tracker.setAcceptForeignTorrents(true);
+ CommunicationManager seeder = createClient();
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 24);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+ seeder.start(InetAddress.getLocalHost());
+ seeder.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ final AtomicInteger piecesDownloaded = new AtomicInteger(0);
+ final ExecutorService es = Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
+ final ExecutorService validatorES = Executors.newFixedThreadPool(4);
+ CommunicationManager leecher = new CommunicationManager(es, validatorES) {
+ @Override
+ public void handlePieceCompleted(SharingPeer peer, Piece piece) {
+ piecesDownloaded.incrementAndGet();
+ try {
+ Thread.sleep(piecesDownloaded.get() * 500);
+ } catch (InterruptedException ignored) {
+
+ }
+ }
+
+ @Override
+ public void stop(int timeout, TimeUnit timeUnit) {
+ super.stop(timeout, timeUnit);
+ es.shutdown();
+ validatorES.shutdown();
+ }
+ };
+ communicationManagerList.add(leecher);
+ leecher.start(InetAddress.getLocalHost());
+ try {
+ TorrentManager torrentManager = leecher.addTorrent(torrentFile.getAbsolutePath(), tempFiles.createTempDir().getAbsolutePath());
+ waitDownloadComplete(torrentManager, 7);
+ fail("Must fail, because file wasn't downloaded completely");
+ } catch (RuntimeException ignored) {
+ }
+ }
+
+ public void canStartAndStopClientTwice() throws Exception {
+ final ExecutorService es = Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
+ final ExecutorService validatorES = Executors.newFixedThreadPool(4);
+ final CommunicationManager communicationManager = new CommunicationManager(es, validatorES);
+ communicationManagerList.add(communicationManager);
+ try {
+ communicationManager.start(InetAddress.getLocalHost());
+ communicationManager.stop();
+ communicationManager.start(InetAddress.getLocalHost());
+ communicationManager.stop();
+ } finally {
+ es.shutdown();
+ validatorES.shutdown();
+ }
+ }
+
+ public void peer_dies_during_download() throws InterruptedException, IOException {
+ tracker.setAnnounceInterval(5);
+ final CommunicationManager seed1 = createClient();
+ final CommunicationManager seed2 = createClient();
+
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 240);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, tracker.getAnnounceURI(), "Test");
+
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+ seed1.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ seed1.start(InetAddress.getLocalHost());
+ seed1.setAnnounceInterval(5);
+ seed2.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ seed2.start(InetAddress.getLocalHost());
+ seed2.setAnnounceInterval(5);
+
+ CommunicationManager leecher = createClient();
+ leecher.start(InetAddress.getLocalHost());
+ leecher.setAnnounceInterval(5);
+ final ExecutorService service = Executors.newFixedThreadPool(1);
+ final Future<?> future = service.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(5 * 1000);
+ seed1.removeTorrent(torrent.getHexInfoHash());
+ Thread.sleep(3 * 1000);
+ seed1.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ seed2.removeTorrent(torrent.getHexInfoHash());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ try {
+ TorrentManager torrentManager = leecher.addTorrent(torrentFile.getAbsolutePath(), tempFiles.createTempDir().getAbsolutePath());
+ waitDownloadComplete(torrentManager, 60);
+ } finally {
+ future.cancel(true);
+ service.shutdown();
+ }
+ }
+
+ public void torrentListenersPositiveTest() throws Exception {
+ tracker.setAcceptForeignTorrents(true);
+ CommunicationManager seeder = createClient();
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 24 + 1);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+ seeder.start(InetAddress.getLocalHost());
+ seeder.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ CommunicationManager leecher = createClient();
+ leecher.start(InetAddress.getLocalHost());
+ final AtomicInteger pieceLoadedInvocationCount = new AtomicInteger();
+ final AtomicInteger connectedInvocationCount = new AtomicInteger();
+ final Semaphore disconnectedLock = new Semaphore(0);
+ TorrentManager torrentManager = leecher.addTorrent(torrentFile.getAbsolutePath(), tempFiles.createTempDir().getAbsolutePath());
+ final AtomicLong totalDownloaded = new AtomicLong();
+ torrentManager.addListener(new TorrentListenerWrapper() {
+ @Override
+ public void pieceDownloaded(PieceInformation pieceInformation, PeerInformation peerInformation) {
+ totalDownloaded.addAndGet(pieceInformation.getSize());
+ pieceLoadedInvocationCount.incrementAndGet();
+ }
+
+ @Override
+ public void peerConnected(PeerInformation peerInformation) {
+ connectedInvocationCount.incrementAndGet();
+ }
+
+ @Override
+ public void peerDisconnected(PeerInformation peerInformation) {
+ disconnectedLock.release();
+ }
+ });
+ waitDownloadComplete(torrentManager, 5);
+ assertEquals(pieceLoadedInvocationCount.get(), torrent.getPiecesCount());
+ assertEquals(connectedInvocationCount.get(), 1);
+ assertEquals(totalDownloaded.get(), dwnlFile.length());
+ if (!disconnectedLock.tryAcquire(10, TimeUnit.SECONDS)) {
+ fail("connection with seeder must be closed after download");
+ }
+ }
+
+ public void testClosingPieceStorageWhenDownloading() throws Exception {
+
+ tracker.setAcceptForeignTorrents(true);
+ final CommunicationManager seeder = createAndStartClient();
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 24);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+ final PieceStorage seederStorage = FullyPieceStorageFactory.INSTANCE.createStorage(torrent, new FileStorage(dwnlFile, 0, dwnlFile.length()));
+ seeder.addTorrent(new TorrentMetadataProvider() {
+ @NotNull
+ @Override
+ public TorrentMetadata getTorrentMetadata() {
+ return torrent;
+ }
+ }, seederStorage);
+
+ CommunicationManager leecher = createAndStartClient();
+
+ final PieceStorage leecherStorage = EmptyPieceStorageFactory.INSTANCE.createStorage(torrent, new FileStorage(tempFiles.createTempFile(), 0, dwnlFile.length()));
+ TorrentManager torrentManager = leecher.addTorrent(new TorrentMetadataProvider() {
+ @NotNull
+ @Override
+ public TorrentMetadata getTorrentMetadata() {
+ return torrent;
+ }
+ }, leecherStorage);
+
+ final AtomicReference<Throwable> exceptionHolder = new AtomicReference<Throwable>();
+ torrentManager.addListener(new TorrentListenerWrapper() {
+ @Override
+ public void pieceDownloaded(PieceInformation pieceInformation, PeerInformation peerInformation) {
+ try {
+ seederStorage.close();
+ leecherStorage.close();
+ } catch (IOException e) {
+ exceptionHolder.set(e);
+ }
+ }
+ });
+
+ waitDownloadComplete(torrentManager, 10);
+ Throwable throwable = exceptionHolder.get();
+ if (throwable != null) {
+ fail("", throwable);
+ }
+ }
+
+ public void testListenersWithBadSeeder() throws Exception {
+ tracker.setAcceptForeignTorrents(true);
+ CommunicationManager seeder = createClient();
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 240);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+ seeder.start(InetAddress.getLocalHost());
+ RandomAccessFile raf = new RandomAccessFile(dwnlFile, "rw");
+ //changing one byte in file. So one piece
+ try {
+ long pos = dwnlFile.length() / 2;
+ raf.seek(pos);
+ int oldByte = raf.read();
+ raf.seek(pos);
+ raf.write(oldByte + 1);
+ } finally {
+ raf.close();
+ }
+ seeder.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent(), FullyPieceStorageFactory.INSTANCE);
+ CommunicationManager leecher = createClient();
+ leecher.start(InetAddress.getLocalHost());
+ final AtomicInteger pieceLoadedInvocationCount = new AtomicInteger();
+ TorrentManager torrentManager = leecher.addTorrent(torrentFile.getAbsolutePath(), tempFiles.createTempDir().getAbsolutePath());
+ torrentManager.addListener(new TorrentListenerWrapper() {
+ @Override
+ public void pieceDownloaded(PieceInformation pieceInformation, PeerInformation peerInformation) {
+ pieceLoadedInvocationCount.incrementAndGet();
+ }
+ });
+ try {
+ waitDownloadComplete(torrentManager, 15);
+ fail("Downloading must be failed because seeder doesn't have valid piece");
+ } catch (RuntimeException ignored) {
+ }
+ assertEquals(pieceLoadedInvocationCount.get(), torrent.getPiecesCount() - 1);
+ }
+
+ public void interrupt_download() throws IOException, InterruptedException {
+ tracker.setAcceptForeignTorrents(true);
+ final CommunicationManager seeder = createClient();
+ final File dwnlFile = tempFiles.createTempFile(513 * 1024 * 60);
+ final TorrentMetadata torrent = TorrentCreator.create(dwnlFile, null, tracker.getAnnounceURI(), "Test");
+
+ final File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+ seeder.start(InetAddress.getLocalHost());
+ seeder.addTorrent(torrentFile.getAbsolutePath(), dwnlFile.getParent());
+ final CommunicationManager leecher = createClient();
+ leecher.start(InetAddress.getLocalHost());
+ final AtomicBoolean interrupted = new AtomicBoolean();
+ final Thread th = new Thread() {
+ @Override
+ public void run() {
+ try {
+ TorrentManager torrentManager = leecher.addTorrent(torrentFile.getAbsolutePath(), tempFiles.createTempDir().getAbsolutePath());
+ waitDownloadComplete(torrentManager, 30);
+ } catch (ClosedByInterruptException e) {
+ interrupted.set(true);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ interrupted.set(true);
+ }
+ }
+ };
+ th.start();
+ Thread.sleep(100);
+ th.interrupt();
+ new WaitFor(10 * 1000) {
+ @Override
+ protected boolean condition() {
+ return !th.isAlive();
+ }
+ };
+
+ assertTrue(interrupted.get());
+ }
+
+ public void test_connect_to_unknown_host() throws InterruptedException, IOException {
+ final File torrent = new File("src/test/resources/torrents/file1.jar.torrent");
+ final TrackedTorrent tt = TrackedTorrent.load(torrent);
+ final CommunicationManager seeder = createAndStartClient();
+ final CommunicationManager leecher = createAndStartClient();
+ final TrackedTorrent announce = tracker.announce(tt);
+ final Random random = new Random();
+ final File leechFolder = tempFiles.createTempDir();
+
+ for (int i = 0; i < 40; i++) {
+ byte[] data = new byte[20];
+ random.nextBytes(data);
+ announce.addPeer(new TrackedPeer(tt, "my_unknown_and_unreachablehost" + i, 6881, ByteBuffer.wrap(data)));
+ }
+ File torrentFile = new File(TEST_RESOURCES + "/torrents", "file1.jar.torrent");
+ File parentFiles = new File(TEST_RESOURCES + "/parentFiles");
+ seeder.addTorrent(torrentFile.getAbsolutePath(), parentFiles.getAbsolutePath());
+ leecher.addTorrent(torrentFile.getAbsolutePath(), leechFolder.getAbsolutePath());
+ waitForFileInDir(leechFolder, "file1.jar");
+ }
+
+ public void test_seeding_does_not_change_file_modification_date() throws IOException, InterruptedException {
+ File srcFile = tempFiles.createTempFile(1024);
+ long time = srcFile.lastModified();
+
+ Thread.sleep(1000);
+
+ CommunicationManager seeder = createAndStartClient();
+
+ final TorrentMetadata torrent = TorrentCreator.create(srcFile, null, tracker.getAnnounceURI(), "Test");
+
+ File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+ seeder.addTorrent(torrentFile.getAbsolutePath(), srcFile.getParent(), FullyPieceStorageFactory.INSTANCE);
+
+ final File downloadDir = tempFiles.createTempDir();
+ CommunicationManager leech = createClient();
+ leech.addTorrent(torrentFile.getAbsolutePath(), downloadDir.getAbsolutePath());
+
+ leech.start(InetAddress.getLocalHost());
+
+ waitForFileInDir(downloadDir, srcFile.getName());
+
+ assertEquals(time, srcFile.lastModified());
+ }
+
+ private void downloadAndStop(TorrentMetadata torrent, long timeout, final CommunicationManager leech) throws IOException {
+ final File tempDir = tempFiles.createTempDir();
+ File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+ leech.addTorrent(torrentFile.getAbsolutePath(), tempDir.getAbsolutePath());
+ leech.start(InetAddress.getLocalHost());
+
+ waitForFileInDir(tempDir, torrent.getFiles().get(0).getRelativePathAsString());
+
+ leech.stop();
+ }
+
+ private void validateMultipleClientsResults(final List<CommunicationManager> clientsList,
+ MessageDigest md5,
+ final File baseFile,
+ String baseMD5,
+ final String hash,
+ final List<File> targetFiles) throws IOException {
+
+ final WaitFor waitFor = new WaitFor(75 * 1000) {
+ @Override
+ protected boolean condition() {
+ boolean retval = true;
+ for (int i = 0; i < clientsList.size(); i++) {
+ if (!retval) return false;
+ File target = targetFiles.get(i);
+ retval = target.isFile();
+ }
+ return retval;
+ }
+ };
+
+ assertTrue(waitFor.isMyResult(), "All seeders didn't get their files");
+ // check file contents here:
+ for (int i = 0; i < clientsList.size(); i++) {
+ final LoadedTorrent torrent = communicationManagerList.get(i).getTorrentsStorage().getLoadedTorrent(hash);
+ final File file = targetFiles.get(i);
+ assertEquals(baseMD5, getFileMD5(file, md5), String.format("MD5 hash is invalid. C:%s, O:%s ",
+ file.getAbsolutePath(), baseFile.getAbsolutePath()));
+ }
+ }
+
+ public void testManySeeders() throws Exception {
+ File artifact = tempFiles.createTempFile(256 * 1024 * 1024);
+ int seedersCount = 15;
+ TorrentMetadata torrent = TorrentCreator.create(artifact, this.tracker.getAnnounceURI(), "test");
+ File torrentFile = tempFiles.createTempFile();
+ saveTorrent(torrent, torrentFile);
+ ServerChannelRegister serverChannelRegister = new FirstAvailableChannel(6881, 10000);
+ for (int i = 0; i < seedersCount; i++) {
+ CommunicationManager seeder = createClient();
+ seeder.addTorrent(torrentFile.getAbsolutePath(), artifact.getParent(), FullyPieceStorageFactory.INSTANCE);
+ seeder.start(new InetAddress[]{InetAddress.getLocalHost()},
+ Constants.DEFAULT_ANNOUNCE_INTERVAL_SEC,
+ null,
+ new SelectorFactoryImpl(),
+ serverChannelRegister);
+ }
+
+ CommunicationManager leecher = createClient();
+ leecher.start(new InetAddress[]{InetAddress.getLocalHost()},
+ Constants.DEFAULT_ANNOUNCE_INTERVAL_SEC,
+ null,
+ new SelectorFactoryImpl(),
+ serverChannelRegister);
+ TorrentManager torrentManager = leecher.addTorrent(torrentFile.getAbsolutePath(),
+ tempFiles.createTempDir().getAbsolutePath(),
+ EmptyPieceStorageFactory.INSTANCE);
+ waitDownloadComplete(torrentManager, 60);
+ }
+
+ private String createMultipleSeedersWithDifferentPieces(File baseFile, int piecesCount, int pieceSize, int numSeeders,
+ List<CommunicationManager> communicationManagerList, List<File> targetFiles) throws IOException, InterruptedException, URISyntaxException {
+
+ List<byte[]> piecesList = new ArrayList<byte[]>(piecesCount);
+ FileInputStream fin = new FileInputStream(baseFile);
+ for (int i = 0; i < piecesCount; i++) {
+ byte[] piece = new byte[pieceSize];
+ fin.read(piece);
+ piecesList.add(piece);
+ }
+ fin.close();
+
+ final long torrentFileLength = baseFile.length();
+ TorrentMetadata torrent = TorrentCreator.create(baseFile, null, this.tracker.getAnnounceURI(), null, "Test", pieceSize);
+ File torrentFile = new File(baseFile.getParentFile(), baseFile.getName() + ".torrent");
+ saveTorrent(torrent, torrentFile);
+
+
+ for (int i = 0; i < numSeeders; i++) {
+ final File baseDir = tempFiles.createTempDir();
+ targetFiles.add(new File(baseDir, baseFile.getName()));
+ final File seederPiecesFile = new File(baseDir, baseFile.getName());
+ RandomAccessFile raf = new RandomAccessFile(seederPiecesFile, "rw");
+ raf.setLength(torrentFileLength);
+ for (int pieceIdx = i; pieceIdx < piecesCount; pieceIdx += numSeeders) {
+ raf.seek(pieceIdx * pieceSize);
+ raf.write(piecesList.get(pieceIdx));
+ }
+ CommunicationManager communicationManager = createClient(" communicationManager idx " + i);
+ communicationManagerList.add(communicationManager);
+ communicationManager.addTorrent(torrentFile.getAbsolutePath(), baseDir.getAbsolutePath());
+ communicationManager.start(InetAddress.getLocalHost());
+ }
+ return torrent.getHexInfoHash();
+ }
+
+ private String getFileMD5(File file, MessageDigest digest) throws IOException {
+ DigestInputStream dIn = new DigestInputStream(new FileInputStream(file), digest);
+ while (dIn.read() >= 0) ;
+ return dIn.getMessageDigest().toString();
+ }
+
+ private void waitForFileInDir(final File downloadDir, final String fileName) {
+ new WaitFor() {
+ @Override
+ protected boolean condition() {
+ return new File(downloadDir, fileName).isFile();
+ }
+ };
+
+ assertTrue(new File(downloadDir, fileName).isFile());
+ }
+
+
+ @AfterMethod
+ protected void tearDown() throws Exception {
+ for (CommunicationManager communicationManager : communicationManagerList) {
+ communicationManager.stop();
+ }
+ stopTracker();
+ tempFiles.cleanup();
+ }
+
+ private void startTracker() throws IOException {
+ int port = 6969;
+ this.tracker = new Tracker(port, "http://" + InetAddress.getLocalHost().getHostAddress() + ":" + port + "" + ANNOUNCE_URL);
+ tracker.setAnnounceInterval(5);
+ this.tracker.start(true);
+ }
+
+ private CommunicationManager createAndStartClient() throws IOException, InterruptedException {
+ CommunicationManager communicationManager = createClient();
+ communicationManager.start(InetAddress.getLocalHost());
+ return communicationManager;
+ }
+
+ private CommunicationManager createClient(String name) {
+ final CommunicationManager communicationManager = communicationManagerFactory.getClient(name);
+ communicationManagerList.add(communicationManager);
+ return communicationManager;
+ }
+
+ private CommunicationManager createClient() {
+ return createClient("");
+ }
+
+ private void stopTracker() {
+ this.tracker.stop();
+ }
+
+ private void assertFilesEqual(File f1, File f2) throws IOException {
+ assertEquals(f1.length(), f2.length(), "Files sizes differ");
+ Checksum c1 = FileUtils.checksum(f1, new CRC32());
+ Checksum c2 = FileUtils.checksum(f2, new CRC32());
+ assertEquals(c1.getValue(), c2.getValue());
+ }
+}
diff --git a/ttorrent-master/tests/src/test/java/com/turn/ttorrent/client/announce/TrackerClientTest.java b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/client/announce/TrackerClientTest.java
new file mode 100644
index 0000000..8a15b59
--- /dev/null
+++ b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/client/announce/TrackerClientTest.java
@@ -0,0 +1,104 @@
+package com.turn.ttorrent.client.announce;
+
+import com.turn.ttorrent.Utils;
+import com.turn.ttorrent.common.AnnounceableInformation;
+import com.turn.ttorrent.common.Peer;
+import com.turn.ttorrent.common.TorrentUtils;
+import com.turn.ttorrent.common.protocol.AnnounceRequestMessage;
+import com.turn.ttorrent.tracker.Tracker;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.mockito.ArgumentMatchers;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+
+@Test
+public class TrackerClientTest {
+
+ private Tracker tracker;
+
+ public TrackerClientTest() {
+ if (Logger.getRootLogger().getAllAppenders().hasMoreElements())
+ return;
+ BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("[%d{MMdd HH:mm:ss,SSS}] %6p - %20.20c - %m %n")));
+ Logger.getRootLogger().setLevel(Utils.getLogLevel());
+ }
+
+ @BeforeMethod
+ protected void setUp() throws Exception {
+ startTracker();
+ }
+
+
+ @Test
+ public void multiAnnounceTest() throws AnnounceException, ConnectException {
+ List<Peer> peers = Collections.singletonList(new Peer(new InetSocketAddress("127.0.0.1", 6881), ByteBuffer.allocate(1)));
+ final URI trackerURI = URI.create("http://localhost:6969/announce");
+ TrackerClient client = new HTTPTrackerClient(peers, trackerURI);
+
+ final AnnounceableInformation firstTorrent = getMockedTorrent(new byte[]{1, 2, 3, 4});
+ final AnnounceableInformation secondTorrent = getMockedTorrent(new byte[]{1, 3, 3, 2});
+ List<AnnounceableInformation> torrents = Arrays.asList(firstTorrent, secondTorrent);
+
+ client.multiAnnounce(AnnounceRequestMessage.RequestEvent.STARTED, true, torrents, peers);
+
+ peers = Collections.singletonList(new Peer(new InetSocketAddress("127.0.0.1", 6882), ByteBuffer.allocate(1)));
+ client.multiAnnounce(AnnounceRequestMessage.RequestEvent.STARTED, true, torrents, peers);
+
+ List<Peer> leecher = Collections.singletonList(new Peer(new InetSocketAddress("127.0.0.1", 6885), ByteBuffer.allocate(1)));
+ final AnnounceableInformation firstTorrentLeech = getMockedTorrent(new byte[]{1, 2, 3, 4});
+ final AnnounceableInformation secondTorrentLeech = getMockedTorrent(new byte[]{1, 3, 3, 2});
+ when(firstTorrentLeech.getLeft()).thenReturn(10L);
+ when(secondTorrentLeech.getLeft()).thenReturn(10L);
+
+ AnnounceResponseListener listener = mock(AnnounceResponseListener.class);
+
+ client.register(listener);
+ client.multiAnnounce(AnnounceRequestMessage.RequestEvent.STARTED, false,
+ Arrays.asList(secondTorrentLeech, firstTorrentLeech), leecher);
+
+ verify(listener, times(2)).handleAnnounceResponse(anyInt(), anyInt(), anyInt(), anyString());
+ verify(listener, times(2)).handleDiscoveredPeers(ArgumentMatchers.<Peer>anyList(), anyString());
+
+ }
+
+ private AnnounceableInformation getMockedTorrent(byte[] hash) {
+ final AnnounceableInformation result = mock(AnnounceableInformation.class);
+ when(result.getLeft()).thenReturn(0L);
+ when(result.getDownloaded()).thenReturn(0L);
+ when(result.getUploaded()).thenReturn(0L);
+ when(result.getInfoHash()).thenReturn(hash);
+ when(result.getHexInfoHash()).thenReturn(TorrentUtils.byteArrayToHexString(hash));
+ return result;
+ }
+
+ private void startTracker() throws IOException {
+ this.tracker = new Tracker(6969);
+ tracker.setAnnounceInterval(5);
+ tracker.setPeerCollectorExpireTimeout(10);
+ this.tracker.start(true);
+ }
+
+ private void stopTracker() {
+ this.tracker.stop();
+ }
+
+ @AfterMethod
+ protected void tearDown() throws Exception {
+ stopTracker();
+ }
+}
diff --git a/ttorrent-master/tests/src/test/java/com/turn/ttorrent/common/TorrentTest.java b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/common/TorrentTest.java
new file mode 100644
index 0000000..679c52f
--- /dev/null
+++ b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/common/TorrentTest.java
@@ -0,0 +1,77 @@
+package com.turn.ttorrent.common;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.io.FileUtils;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.testng.Assert.*;
+
+@Test
+public class TorrentTest {
+
+ public void test_create_torrent() throws URISyntaxException, IOException, InterruptedException {
+ URI announceURI = new URI("http://localhost:6969/announce");
+ String createdBy = "Test";
+ TorrentMetadata t = TorrentCreator.create(new File("src/test/resources/parentFiles/file1.jar"), announceURI, createdBy);
+ assertEquals(createdBy, t.getCreatedBy().get());
+ assertEquals(announceURI.toString(), t.getAnnounce());
+ }
+
+ public void load_torrent_created_by_utorrent() throws IOException {
+ TorrentMetadata t = new TorrentParser().parseFromFile(new File("src/test/resources/torrents/file1.jar.torrent"));
+ assertEquals("http://localhost:6969/announce", t.getAnnounce());
+ assertEquals("B92D38046C76D73948E14C42DF992CAF25489D08", t.getHexInfoHash());
+ assertEquals("uTorrent/3130", t.getCreatedBy().get());
+ }
+
+ public void torrent_from_multiple_files() throws URISyntaxException, InterruptedException, IOException {
+ URI announceURI = new URI("http://localhost:6969/announce");
+ String createdBy = "Test2";
+ final File parentDir = new File("src/test/resources/parentFiles/parentDir");
+ final long creationTimeSecs = 1376051000;
+ final String[] fileNames = new String[]
+ {"AccuRevCommon.jar",
+ "commons-io-cio2.5_3.jar",
+ "commons-io-cio2.5_3.jar.link",
+ "inDir/application.wadl",
+ "storage.version"};
+ final List<File> files = new ArrayList<File>();
+ for (String fileName : fileNames) {
+ files.add(new File(parentDir, fileName));
+ }
+ TorrentMetadata createdTorrent = TorrentCreator.create(parentDir, files, announceURI, null, createdBy, creationTimeSecs, TorrentCreator.DEFAULT_PIECE_LENGTH);
+ File torrentFileWin = new File("src/test/resources/torrents/parentDir.win.torrent");
+ File torrentFileLinux = new File("src/test/resources/torrents/parentDir.linux.torrent");
+ final byte[] expectedBytesWin = FileUtils.readFileToByteArray(torrentFileWin);
+ final byte[] expectedBytesLinux = FileUtils.readFileToByteArray(torrentFileLinux);
+ final byte[] actualBytes = new TorrentSerializer().serialize(createdTorrent);
+
+ assertTrue(Hex.encodeHexString(expectedBytesWin).equals(Hex.encodeHexString(actualBytes)) || Hex.encodeHexString(expectedBytesLinux).equals(Hex.encodeHexString(actualBytes)));
+ }
+
+ public void testFilenames() throws IOException {
+ File torrentFile = new File("src/test/resources/torrents/parentDir.win.torrent");
+ TorrentMetadata t2 = new TorrentParser().parseFromFile(torrentFile);
+ final List<TorrentFile> tmpFileNames = t2.getFiles();
+ final List<String> normalizedFilenames = new ArrayList<String>(tmpFileNames.size());
+ for (TorrentFile torrentFileInfo : tmpFileNames) {
+ normalizedFilenames.add(t2.getDirectoryName() + "/" + torrentFileInfo.getRelativePathAsString().replaceAll("\\\\", "/"));
+ }
+ String[] expectedFilenames = new String[]
+ {"parentDir/AccuRevCommon.jar",
+ "parentDir/commons-io-cio2.5_3.jar",
+ "parentDir/commons-io-cio2.5_3.jar.link",
+ "parentDir/inDir/application.wadl",
+ "parentDir/storage.version"};
+ assertEqualsNoOrder(normalizedFilenames.toArray(new String[normalizedFilenames.size()]), expectedFilenames);
+ System.out.println();
+ }
+
+}
diff --git a/ttorrent-master/tests/src/test/java/com/turn/ttorrent/tracker/TrackerAnnounceTest.java b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/tracker/TrackerAnnounceTest.java
new file mode 100644
index 0000000..4aad8c2
--- /dev/null
+++ b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/tracker/TrackerAnnounceTest.java
@@ -0,0 +1,38 @@
+package com.turn.ttorrent.tracker;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+
+import static com.turn.ttorrent.tracker.TrackerUtils.loadTorrent;
+import static org.testng.Assert.assertEquals;
+
+@Test
+public class TrackerAnnounceTest {
+
+ private Tracker tracker;
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ this.tracker = new Tracker(6969);
+ tracker.setAnnounceInterval(5);
+ tracker.setPeerCollectorExpireTimeout(10);
+ this.tracker.start(false);
+ }
+
+ public void test_announce() throws IOException {
+
+ assertEquals(0, this.tracker.getTrackedTorrents().size());
+
+ this.tracker.announce(loadTorrent("file1.jar.torrent"));
+
+ assertEquals(1, this.tracker.getTrackedTorrents().size());
+ }
+
+ @AfterMethod
+ public void tearDown() throws Exception {
+ this.tracker.stop();
+ }
+}
diff --git a/ttorrent-master/tests/src/test/java/com/turn/ttorrent/tracker/TrackerTest.java b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/tracker/TrackerTest.java
new file mode 100644
index 0000000..39d6802
--- /dev/null
+++ b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/tracker/TrackerTest.java
@@ -0,0 +1,422 @@
+package com.turn.ttorrent.tracker;
+
+import com.turn.ttorrent.CommunicationManagerFactory;
+import com.turn.ttorrent.TempFiles;
+import com.turn.ttorrent.Utils;
+import com.turn.ttorrent.WaitFor;
+import com.turn.ttorrent.client.CommunicationManager;
+import com.turn.ttorrent.client.SharedTorrent;
+import com.turn.ttorrent.client.storage.FairPieceStorageFactory;
+import com.turn.ttorrent.client.storage.FileCollectionStorage;
+import com.turn.ttorrent.common.*;
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.*;
+import java.util.*;
+import java.util.zip.CRC32;
+import java.util.zip.Checksum;
+
+import static com.turn.ttorrent.tracker.Tracker.ANNOUNCE_URL;
+import static com.turn.ttorrent.tracker.TrackerUtils.TEST_RESOURCES;
+import static org.testng.Assert.*;
+
+@Test
+public class TrackerTest {
+
+ private Tracker tracker;
+ private TempFiles tempFiles;
+ // private String myLogfile;
+ private List<CommunicationManager> communicationManagerList = new ArrayList<CommunicationManager>();
+
+ private final CommunicationManagerFactory communicationManagerFactory;
+
+
+ public TrackerTest() {
+ communicationManagerFactory = new CommunicationManagerFactory();
+ if (Logger.getRootLogger().getAllAppenders().hasMoreElements())
+ return;
+ BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("[%d{MMdd HH:mm:ss,SSS}] %6p - %20.20c - %m %n")));
+ Logger.getRootLogger().setLevel(Utils.getLogLevel());
+ }
+
+ @BeforeMethod
+ protected void setUp() throws Exception {
+ tempFiles = new TempFiles();
+ startTracker();
+ }
+
+ public void test_tracker_all_ports() throws IOException {
+ final int port = tracker.getAnnounceURI().getPort();
+ final Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
+ while (e.hasMoreElements()) {
+ final NetworkInterface ni = e.nextElement();
+ final Enumeration<InetAddress> addresses = ni.getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ final InetAddress addr = addresses.nextElement();
+ try {
+ Socket s = new Socket(addr, port);
+ s.close();
+ } catch (Exception ex) {
+ if (System.getProperty("java.version").startsWith("1.7.") || addr instanceof Inet4Address) {
+ fail("Unable to connect to " + addr, ex);
+ }
+ }
+ }
+
+ }
+ }
+
+ public void testPeerWithManyInterfaces() throws Exception {
+ List<InetAddress> selfAddresses = new ArrayList<InetAddress>();
+ final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+ while (networkInterfaces.hasMoreElements()) {
+ NetworkInterface ni = networkInterfaces.nextElement();
+ final Enumeration<InetAddress> inetAddresses = ni.getInetAddresses();
+ while (inetAddresses.hasMoreElements()) {
+ InetAddress inetAddress = inetAddresses.nextElement();
+ if (inetAddress instanceof Inet6Address) continue;// ignore IPv6 addresses
+
+ selfAddresses.add(inetAddress);
+ }
+ }
+
+ final InetAddress[] inetAddresses = selfAddresses.toArray(new InetAddress[selfAddresses.size()]);
+ CommunicationManager seeder = createCommunicationManager();
+ File torrentFile = new File(TEST_RESOURCES + "/torrents", "file1.jar.torrent");
+ File parentFiles = new File(TEST_RESOURCES + "/parentFiles");
+ final String hexInfoHash = seeder.addTorrent(torrentFile.getAbsolutePath(), parentFiles.getAbsolutePath()).getHexInfoHash();
+ seeder.start(inetAddresses);
+ final WaitFor waitFor = new WaitFor(10000) {
+ @Override
+ protected boolean condition() {
+ final TrackedTorrent trackedTorrent = tracker.getTrackedTorrent(hexInfoHash);
+ return trackedTorrent != null && trackedTorrent.getPeers().size() >= inetAddresses.length;
+ }
+ };
+
+ assertTrue(waitFor.isMyResult());
+
+ final TrackedTorrent trackedTorrent = tracker.getTrackedTorrent(hexInfoHash);
+
+ Set<String> expectedIps = new HashSet<String>();
+ for (InetAddress inetAddress : inetAddresses) {
+ expectedIps.add(inetAddress.getHostAddress());
+ }
+ Set<String> actualIps = new HashSet<String>();
+ for (TrackedPeer peer : trackedTorrent.getPeers().values()) {
+ actualIps.add(peer.getIp());
+ }
+
+ assertEquals(actualIps, expectedIps);
+ assertEquals(inetAddresses.length, actualIps.size());
+
+ }
+
+ public void test_share_and_download() throws IOException, InterruptedException {
+ final TrackedTorrent tt = this.tracker.announce(loadTorrent("file1.jar.torrent"));
+ assertEquals(0, tt.getPeers().size());
+
+ CommunicationManager seeder = createCommunicationManager();
+ File torrentFile = new File(TEST_RESOURCES + "/torrents", "file1.jar.torrent");
+ File parentFiles = new File(TEST_RESOURCES + "/parentFiles");
+ seeder.addTorrent(torrentFile.getAbsolutePath(), parentFiles.getAbsolutePath());
+
+ assertEquals(tt.getHexInfoHash(), seeder.getTorrentsStorage().announceableTorrents().iterator().next().getHexInfoHash());
+
+ final File downloadDir = tempFiles.createTempDir();
+ CommunicationManager leech = createCommunicationManager();
+ leech.addTorrent(torrentFile.getAbsolutePath(), downloadDir.getAbsolutePath());
+
+ try {
+ seeder.start(InetAddress.getLocalHost());
+
+ leech.start(InetAddress.getLocalHost());
+
+ waitForFileInDir(downloadDir, "file1.jar");
+ assertFilesEqual(new File(TEST_RESOURCES + "/parentFiles/file1.jar"), new File(downloadDir, "file1.jar"));
+ } finally {
+ leech.stop();
+ seeder.stop();
+ }
+ }
+
+ public void tracker_accepts_torrent_from_seeder() throws IOException, InterruptedException {
+ this.tracker.setAcceptForeignTorrents(true);
+ CommunicationManager seeder = createCommunicationManager();
+ File torrentFile = new File(TEST_RESOURCES + "/torrents", "file1.jar.torrent");
+ File parentFiles = new File(TEST_RESOURCES + "/parentFiles");
+ seeder.addTorrent(torrentFile.getAbsolutePath(), parentFiles.getAbsolutePath());
+
+ try {
+ seeder.start(InetAddress.getLocalHost());
+
+ waitForSeeder(seeder.getTorrentsStorage().announceableTorrents().iterator().next().getInfoHash());
+
+ Collection<TrackedTorrent> trackedTorrents = this.tracker.getTrackedTorrents();
+ assertEquals(1, trackedTorrents.size());
+
+ TrackedTorrent trackedTorrent = trackedTorrents.iterator().next();
+ Map<PeerUID, TrackedPeer> peers = trackedTorrent.getPeers();
+ assertEquals(1, peers.size());
+ assertTrue(peers.values().iterator().next().isCompleted()); // seed
+ assertEquals(1, trackedTorrent.seeders());
+ assertEquals(0, trackedTorrent.leechers());
+ } finally {
+ seeder.stop();
+ }
+ }
+
+ public void tracker_accepts_torrent_from_leech() throws IOException, InterruptedException {
+ this.tracker.setAcceptForeignTorrents(true);
+
+ final File downloadDir = tempFiles.createTempDir();
+ CommunicationManager leech = createCommunicationManager();
+ File torrentFile = new File(TEST_RESOURCES + "/torrents", "file1.jar.torrent");
+ leech.addTorrent(torrentFile.getAbsolutePath(), downloadDir.getAbsolutePath());
+
+ try {
+ leech.start(InetAddress.getLocalHost());
+
+ new WaitFor() {
+ @Override
+ protected boolean condition() {
+ for (TrackedTorrent tt : tracker.getTrackedTorrents()) {
+ if (tt.getPeers().size() == 1) return true;
+ }
+
+ return false;
+ }
+ };
+
+ Collection<TrackedTorrent> trackedTorrents = this.tracker.getTrackedTorrents();
+ assertEquals(1, trackedTorrents.size());
+
+ TrackedTorrent trackedTorrent = trackedTorrents.iterator().next();
+ Map<PeerUID, TrackedPeer> peers = trackedTorrent.getPeers();
+ assertEquals(1, peers.size());
+ assertFalse(peers.values().iterator().next().isCompleted()); // leech
+ assertEquals(0, trackedTorrent.seeders());
+ assertEquals(1, trackedTorrent.leechers());
+ } finally {
+ leech.stop();
+ }
+ }
+
+ public void tracker_removes_peer_after_peer_shutdown() throws IOException, InterruptedException {
+ tracker.setAcceptForeignTorrents(true);
+ File torrentFile = new File(TEST_RESOURCES + "/torrents", "file1.jar.torrent");
+ File parentFiles = new File(TEST_RESOURCES + "/parentFiles");
+
+ final CommunicationManager c1 = createCommunicationManager();
+ c1.start(InetAddress.getLocalHost());
+ c1.addTorrent(torrentFile.getAbsolutePath(), parentFiles.getAbsolutePath());
+
+ final CommunicationManager c2 = createCommunicationManager();
+ c2.start(InetAddress.getLocalHost());
+ c2.addTorrent(torrentFile.getAbsolutePath(), parentFiles.getAbsolutePath());
+
+ new WaitFor(10 * 1000) {
+ @Override
+ protected boolean condition() {
+ return tracker.getTrackedTorrents().size() == 1;
+ }
+ };
+
+ final TrackedTorrent tt = tracker.getTrackedTorrents().iterator().next();
+
+ new WaitFor(10 * 1000) {
+ @Override
+ protected boolean condition() {
+ return tt.getPeers().size() == 2;
+ }
+ };
+
+ final InetSocketAddress c1Address = new InetSocketAddress(InetAddress.getLocalHost(), c1.getConnectionManager().getBindPort());
+ final InetSocketAddress c2Address = new InetSocketAddress(InetAddress.getLocalHost(), c2.getConnectionManager().getBindPort());
+ assertTrue(tt.getPeers().containsKey(new PeerUID(c1Address, tt.getHexInfoHash())));
+ assertTrue(tt.getPeers().containsKey(new PeerUID(c2Address, tt.getHexInfoHash())));
+
+ c2.stop();
+ new WaitFor(30 * 1000) {
+
+ @Override
+ protected boolean condition() {
+ return tt.getPeers().size() == 1;
+ }
+ };
+ assertTrue(tt.getPeers().containsKey(new PeerUID(c1Address, tt.getHexInfoHash())));
+ assertFalse(tt.getPeers().containsKey(new PeerUID(c2Address, tt.getHexInfoHash())));
+ }
+
+ public void tracker_removes_peer_after_timeout() throws IOException, InterruptedException {
+ tracker.setAcceptForeignTorrents(true);
+ tracker.stop();
+ tracker.start(true);
+ final SharedTorrent torrent = completeTorrent("file1.jar.torrent");
+ tracker.setPeerCollectorExpireTimeout(5);
+
+ int peerPort = 6885;
+ String peerHost = InetAddress.getLocalHost().getHostAddress();
+ final String announceUrlC1 = "http://localhost:6969/announce?info_hash=%B9-8%04lv%D79H%E1LB%DF%99%2C%AF%25H%9D%08&peer_id=-TO0042-97ec308c9637&" +
+ "port=" + peerPort + "&uploaded=0&downloaded=0&left=0&compact=1&no_peer_id=0&ip=" + peerHost;
+
+ try {
+ final URLConnection connection = new URL(announceUrlC1).openConnection();
+ connection.getInputStream().close();
+ } catch (Exception e) {
+ fail("", e);
+ }
+
+ final CommunicationManager c2 = createCommunicationManager();
+ c2.setAnnounceInterval(120);
+ c2.start(InetAddress.getLocalHost());
+ File torrentFile = new File(TEST_RESOURCES + "/torrents", "file1.jar.torrent");
+ File parentFiles = new File(TEST_RESOURCES + "/parentFiles");
+ c2.addTorrent(torrentFile.getAbsolutePath(), parentFiles.getAbsolutePath());
+
+ final TrackedTorrent tt = tracker.getTrackedTorrent(torrent.getHexInfoHash());
+ new WaitFor(10 * 1000) {
+ @Override
+ protected boolean condition() {
+
+ return tt.getPeers().size() == 2;
+ }
+ };
+
+ final InetSocketAddress c1Address = new InetSocketAddress(peerHost, peerPort);
+ final InetSocketAddress c2Address = new InetSocketAddress(InetAddress.getLocalHost(), c2.getConnectionManager().getBindPort());
+ assertTrue(tt.getPeers().containsKey(new PeerUID(c1Address, tt.getHexInfoHash())));
+ assertTrue(tt.getPeers().containsKey(new PeerUID(c2Address, tt.getHexInfoHash())));
+
+ new WaitFor(30 * 1000) {
+
+ @Override
+ protected boolean condition() {
+ try {
+ final URLConnection connection = new URL(announceUrlC1).openConnection();
+ connection.getInputStream().close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return tt.getPeers().size() == 1;
+ }
+ };
+ assertEquals(tt.getPeers().size(), 1);
+ assertTrue(tt.getPeers().containsKey(new PeerUID(c1Address, tt.getHexInfoHash())));
+ assertFalse(tt.getPeers().containsKey(new PeerUID(c2Address, tt.getHexInfoHash())));
+ }
+
+ // @Test(invocationCount = 50)
+ public void tracker_accepts_torrent_from_seeder_plus_leech() throws IOException, InterruptedException {
+ this.tracker.setAcceptForeignTorrents(true);
+ assertEquals(0, this.tracker.getTrackedTorrents().size());
+
+ CommunicationManager seeder = createCommunicationManager();
+ File torrentFile = new File(TEST_RESOURCES + "/torrents", "file1.jar.torrent");
+ File parentFiles = new File(TEST_RESOURCES + "/parentFiles");
+ seeder.addTorrent(torrentFile.getAbsolutePath(), parentFiles.getAbsolutePath());
+
+ final File downloadDir = tempFiles.createTempDir();
+ CommunicationManager leech = createCommunicationManager();
+ leech.addTorrent(torrentFile.getAbsolutePath(), downloadDir.getAbsolutePath());
+
+ try {
+ seeder.start(InetAddress.getLocalHost());
+ leech.start(InetAddress.getLocalHost());
+
+ waitForFileInDir(downloadDir, "file1.jar");
+ } finally {
+ seeder.stop();
+ leech.stop();
+ }
+ }
+
+ private TrackedTorrent loadTorrent(String name) throws IOException {
+ TorrentMetadata torrentMetadata = new TorrentParser().parseFromFile(new File(TEST_RESOURCES + "/torrents", name));
+ return new TrackedTorrent(torrentMetadata.getInfoHash());
+ }
+
+ private void startTracker() throws IOException {
+ int port = 6969;
+ this.tracker = new Tracker(port, "http://" + InetAddress.getLocalHost().getHostAddress() + ":" + port + "" + ANNOUNCE_URL);
+ tracker.setAnnounceInterval(5);
+ tracker.setPeerCollectorExpireTimeout(10);
+ this.tracker.start(true);
+ }
+
+ private void stopTracker() {
+ this.tracker.stop();
+ }
+
+ @AfterMethod
+ protected void tearDown() throws Exception {
+ for (CommunicationManager communicationManager : communicationManagerList) {
+ communicationManager.stop();
+ }
+ stopTracker();
+ tempFiles.cleanup();
+ }
+
+ private CommunicationManager createCommunicationManager() {
+ final CommunicationManager communicationManager = communicationManagerFactory.getClient("");
+ communicationManagerList.add(communicationManager);
+ return communicationManager;
+ }
+
+ private void waitForFileInDir(final File downloadDir, final String fileName) {
+ new WaitFor() {
+ @Override
+ protected boolean condition() {
+ return new File(downloadDir, fileName).isFile();
+ }
+ };
+
+ assertTrue(new File(downloadDir, fileName).isFile());
+ }
+
+ private SharedTorrent completeTorrent(String name) throws IOException {
+ File torrentFile = new File(TEST_RESOURCES + "/torrents", name);
+ File parentFiles = new File(TEST_RESOURCES + "/parentFiles");
+ TorrentMetadata torrentMetadata = new TorrentParser().parseFromFile(torrentFile);
+ return SharedTorrent.fromFile(torrentFile,
+ FairPieceStorageFactory.INSTANCE.createStorage(torrentMetadata, FileCollectionStorage.create(torrentMetadata, parentFiles)),
+ new TorrentStatistic());
+ }
+
+ private SharedTorrent incompleteTorrent(String name, File destDir) throws IOException {
+ File torrentFile = new File(TEST_RESOURCES + "/torrents", name);
+ TorrentMetadata torrentMetadata = new TorrentParser().parseFromFile(torrentFile);
+ return SharedTorrent.fromFile(torrentFile,
+ FairPieceStorageFactory.INSTANCE.createStorage(torrentMetadata, FileCollectionStorage.create(torrentMetadata, destDir)),
+ new TorrentStatistic());
+ }
+
+ private void waitForSeeder(final byte[] torrentHash) {
+ new WaitFor() {
+ @Override
+ protected boolean condition() {
+ for (TrackedTorrent tt : tracker.getTrackedTorrents()) {
+ if (tt.seeders() == 1 && tt.getHexInfoHash().equals(TorrentUtils.byteArrayToHexString(torrentHash))) return true;
+ }
+
+ return false;
+ }
+ };
+ }
+
+ private void assertFilesEqual(File f1, File f2) throws IOException {
+ assertEquals(f1.length(), f2.length(), "Files sizes differ");
+ Checksum c1 = FileUtils.checksum(f1, new CRC32());
+ Checksum c2 = FileUtils.checksum(f2, new CRC32());
+ assertEquals(c1.getValue(), c2.getValue());
+ }
+}
diff --git a/ttorrent-master/tests/src/test/java/com/turn/ttorrent/tracker/TrackerUtils.java b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/tracker/TrackerUtils.java
new file mode 100644
index 0000000..e7a7ecb
--- /dev/null
+++ b/ttorrent-master/tests/src/test/java/com/turn/ttorrent/tracker/TrackerUtils.java
@@ -0,0 +1,18 @@
+package com.turn.ttorrent.tracker;
+
+import com.turn.ttorrent.common.TorrentMetadata;
+import com.turn.ttorrent.common.TorrentParser;
+
+import java.io.File;
+import java.io.IOException;
+
+public class TrackerUtils {
+
+ public static final String TEST_RESOURCES = "src/test/resources";
+
+ public static TrackedTorrent loadTorrent(String name) throws IOException {
+ TorrentMetadata torrentMetadata = new TorrentParser().parseFromFile(new File(TEST_RESOURCES + "/torrents", name));
+ return new TrackedTorrent(torrentMetadata.getInfoHash());
+ }
+
+}
diff --git a/ttorrent-master/tests/src/test/resources/parentFiles/file1.jar b/ttorrent-master/tests/src/test/resources/parentFiles/file1.jar
new file mode 100644
index 0000000..add2577
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/parentFiles/file1.jar
Binary files differ
diff --git a/ttorrent-master/tests/src/test/resources/parentFiles/file2.jar b/ttorrent-master/tests/src/test/resources/parentFiles/file2.jar
new file mode 100644
index 0000000..95d8e21
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/parentFiles/file2.jar
Binary files differ
diff --git a/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/AccuRevCommon.jar b/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/AccuRevCommon.jar
new file mode 100644
index 0000000..4c5db25
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/AccuRevCommon.jar
Binary files differ
diff --git a/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/commons-io-cio2.5_3.jar b/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/commons-io-cio2.5_3.jar
new file mode 100644
index 0000000..d896ec0
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/commons-io-cio2.5_3.jar
Binary files differ
diff --git a/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/commons-io-cio2.5_3.jar.link b/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/commons-io-cio2.5_3.jar.link
new file mode 100644
index 0000000..6af92ff
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/commons-io-cio2.5_3.jar.link
@@ -0,0 +1,2 @@
+E:\DevData\TeamcityBuilds\8.1\TeamCity-29117\Data\system\artifacts\CommonsIo\one\7\commons-io-cio2.5_3.jar
+E:\DevData\TeamcityBuilds\8.1\TeamCity-29117\Data\system\artifacts\CommonsIo\one\7\.teamcity\torrents\commons-io-cio2.5_3.jar.torrent
\ No newline at end of file
diff --git a/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/inDir/application.wadl b/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/inDir/application.wadl
new file mode 100644
index 0000000..f8655d5
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/inDir/application.wadl
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?><ns2:application xmlns:ns2="http://wadl.dev.java.net/2009/02"><ns2:doc xmlns:jersey="http://jersey.java.net/" jersey:generatedBy="Jersey: 1.16 11/28/2012 02:09 PM"/><ns2:doc title="TeamCity REST API" xml:lang="en">
+ See also http://confluence.jetbrains.net/display/TW/REST+API+Plugin
+ </ns2:doc><ns2:grammars><ns2:include href="application.wadl/xsd1.xsd"><ns2:doc title="Generated" xml:lang="en"/></ns2:include><ns2:include href="application.wadl/xsd0.xsd"><ns2:doc title="Generated" xml:lang="en"/></ns2:include></ns2:grammars><ns2:resources base="http://buildserver.labs.intellij.net/"><ns2:resource path="/app/rest/projects"><ns2:method id="serveProjects" name="GET"><ns2:response><ns2:representation element="projects" mediaType="application/xml"/><ns2:representation element="projects" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="createProject" name="POST"><ns2:request><ns2:representation element="newProjectDescription" mediaType="application/xml"/><ns2:representation element="newProjectDescription" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="project" mediaType="application/xml"/><ns2:representation element="project" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="createEmptyProject" name="POST"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation element="project" mediaType="application/xml"/><ns2:representation element="project" mediaType="application/json"/></ns2:response></ns2:method><ns2:resource path="/{projectLocator}/templates"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:method id="createBuildTypeTemplate" name="POST"><ns2:doc>Creates a new build configuration template by copying existing one.</ns2:doc><ns2:request><ns2:representation element="newBuildTypeDescription" mediaType="application/xml"/><ns2:representation element="newBuildTypeDescription" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="buildType" mediaType="application/xml"/><ns2:representation element="buildType" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="serveTemplatesInProject" name="GET"><ns2:response><ns2:representation element="buildTypes" mediaType="application/xml"/><ns2:representation element="buildTypes" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="createEmptyBuildTypeTemplate" name="POST"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation element="buildType" mediaType="application/xml"/><ns2:representation element="buildType" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/parameters/{name}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="name" style="template" type="xs:string"/><ns2:method id="putParameter" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="serveParameter" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="deleteParameter" name="DELETE"/></ns2:resource><ns2:resource path="/{projectLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:method id="serveProject" name="GET"><ns2:response><ns2:representation element="project" mediaType="application/xml"/><ns2:representation element="project" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteProject" name="DELETE"/></ns2:resource><ns2:resource path="/{projectLocator}/parentProject"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:method id="getParentProject" name="GET"><ns2:response><ns2:representation element="project-ref" mediaType="application/xml"/><ns2:representation element="project-ref" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="setParentProject" name="PUT"><ns2:request><ns2:representation element="project-ref" mediaType="application/xml"/><ns2:representation element="project-ref" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="project-ref" mediaType="application/xml"/><ns2:representation element="project-ref" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/buildTypes"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:method id="createBuildType" name="POST"><ns2:doc>Creates a new build configuration by copying existing one.</ns2:doc><ns2:request><ns2:representation element="newBuildTypeDescription" mediaType="application/xml"/><ns2:representation element="newBuildTypeDescription" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="buildType" mediaType="application/xml"/><ns2:representation element="buildType" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="createEmptyBuildType" name="POST"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation element="buildType" mediaType="application/xml"/><ns2:representation element="buildType" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="serveBuildTypesInProject" name="GET"><ns2:response><ns2:representation element="buildTypes" mediaType="application/xml"/><ns2:representation element="buildTypes" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/buildTypes/{btLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuildType" name="GET"><ns2:response><ns2:representation element="buildType" mediaType="application/xml"/><ns2:representation element="buildType" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:method id="setProjectFiled" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="serveProjectField" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/templates/{btLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuildTypeTemplates" name="GET"><ns2:response><ns2:representation element="buildType" mediaType="application/xml"/><ns2:representation element="buildType" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/parameters"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:method id="serveParameters" name="GET"><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="changeAllParameters" name="PUT"><ns2:request><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteAllParameters" name="DELETE"/></ns2:resource><ns2:resource path="/{projectLocator}/buildTypes/{btLocator}/builds/{buildLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildFieldWithProject" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/buildTypes/{btLocator}/builds/{buildLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildWithProject" name="GET"><ns2:response><ns2:representation element="build" mediaType="application/xml"/><ns2:representation element="build" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/newProjectDescription"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:method id="getExampleNewProjectDescription" name="GET"><ns2:doc>Experimental support only.
+ Use this to get an example of the bean to be posted to the /projects request to create a new project</ns2:doc><ns2:response><ns2:representation element="newProjectDescription" mediaType="application/xml"/><ns2:representation element="newProjectDescription" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/buildTypes/{btLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuildTypeFieldWithProject" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/buildTypes/{btLocator}/builds"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuilds" name="GET"><ns2:doc>Serves builds matching supplied condition.</ns2:doc><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="status" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="triggeredByUser" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includePersonal" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includeCanceled" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="onlyPinned" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="tag" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="agentName" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="sinceBuild" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="sinceDate" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="start" style="query" type="xs:long"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="count" style="query" type="xs:int"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="locator" style="query" type="xs:string"/></ns2:request><ns2:response><ns2:representation element="builds" mediaType="application/xml"/><ns2:representation element="builds" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource></ns2:resource><ns2:resource path="/app/rest/vcs-root-instances"><ns2:method id="serveInstances" name="GET"><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="locator" style="query" type="xs:string"/></ns2:request><ns2:response><ns2:representation element="vcs-root-instances" mediaType="application/xml"/><ns2:representation element="vcs-root-instances" mediaType="application/json"/></ns2:response></ns2:method><ns2:resource path="/{vcsRootInstanceLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootInstanceLocator" style="template" type="xs:string"/><ns2:method id="serveInstance" name="GET"><ns2:response><ns2:representation element="vcs-root-instance" mediaType="application/xml"/><ns2:representation element="vcs-root-instance" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{vcsRootInstanceLocator}/properties"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootInstanceLocator" style="template" type="xs:string"/><ns2:method id="serveRootInstanceProperties" name="GET"><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{vcsRootInstanceLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootInstanceLocator" style="template" type="xs:string"/><ns2:method id="serveInstanceField" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="setInstanceField" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource></ns2:resource><ns2:resource path="/app/rest/users"><ns2:method id="createUser" name="POST"><ns2:request><ns2:representation element="user" mediaType="application/xml"/><ns2:representation element="user" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="user" mediaType="*/*"/></ns2:response></ns2:method><ns2:method id="serveUsers" name="GET"><ns2:response><ns2:representation element="users" mediaType="application/xml"/><ns2:representation element="users" mediaType="application/json"/></ns2:response></ns2:method><ns2:resource path="/{userLocator}/roles"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="userLocator" style="template" type="xs:string"/><ns2:method id="addRole" name="POST"><ns2:request><ns2:representation element="role" mediaType="application/xml"/><ns2:representation element="role" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="role" mediaType="application/xml"/><ns2:representation element="role" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="replaceRoles" name="PUT"><ns2:doc>Replaces user's roles with the submitted ones</ns2:doc><ns2:request><ns2:representation element="roles" mediaType="application/xml"/><ns2:representation element="roles" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="roles" mediaType="application/xml"/><ns2:representation element="roles" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="listRoles" name="GET"><ns2:response><ns2:representation element="roles" mediaType="application/xml"/><ns2:representation element="roles" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{userLocator}/roles/{roleId}/{scope}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="userLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="scope" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="roleId" style="template" type="xs:string"/><ns2:method id="addRoleSimple" name="PUT"><ns2:response><ns2:representation element="role" mediaType="application/xml"/><ns2:representation element="role" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteRole" name="DELETE"/><ns2:method id="listRole" name="GET"><ns2:response><ns2:representation element="role" mediaType="application/xml"/><ns2:representation element="role" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="addRoleSimplePost" name="POST"/></ns2:resource><ns2:resource path="/{userLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="userLocator" style="template" type="xs:string"/><ns2:method id="serveUser" name="GET"><ns2:response><ns2:representation element="user" mediaType="application/xml"/><ns2:representation element="user" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="updateUser" name="PUT"><ns2:request><ns2:representation element="user" mediaType="application/xml"/><ns2:representation element="user" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="user" mediaType="application/xml"/><ns2:representation element="user" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{userLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="userLocator" style="template" type="xs:string"/><ns2:method id="setUserField" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="serveUserField" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{userLocator}/properties"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="userLocator" style="template" type="xs:string"/><ns2:method id="serveUserProperties" name="GET"><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{userLocator}/properties/{name}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="userLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="name" style="template" type="xs:string"/><ns2:method id="serveUserProperties" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="putUserProperty" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="removeUserProperty" name="DELETE"/></ns2:resource></ns2:resource><ns2:resource path="/app/rest/changes"><ns2:method id="serveChanges" name="GET"><ns2:doc>Lists changes by the specified locator</ns2:doc><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="project" style="query" type="xs:string"><ns2:doc>Change locator</ns2:doc></ns2:param><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildType" style="query" type="xs:string"><ns2:doc>Deprecated, use "locator" parameter instead</ns2:doc></ns2:param><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="build" style="query" type="xs:string"><ns2:doc>Deprecated, use "locator" parameter instead</ns2:doc></ns2:param><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRoot" style="query" type="xs:string"><ns2:doc>Deprecated, use "locator" parameter instead</ns2:doc></ns2:param><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="sinceChange" style="query" type="xs:string"><ns2:doc>Deprecated, use "locator" parameter instead. Note that corresponding locator dimension is "vcsRootInstance"</ns2:doc></ns2:param><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="start" style="query" type="xs:long"><ns2:doc>Deprecated, use "locator" parameter instead</ns2:doc></ns2:param><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="count" style="query" type="xs:int"><ns2:doc>Deprecated, use "locator" parameter instead</ns2:doc></ns2:param><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="locator" style="query" type="xs:string"><ns2:doc>Deprecated, use "locator" parameter instead</ns2:doc></ns2:param></ns2:request><ns2:response><ns2:representation element="changes" mediaType="application/xml"/><ns2:representation element="changes" mediaType="application/json"/></ns2:response></ns2:method><ns2:resource path="/{changeLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="changeLocator" style="template" type="xs:string"/><ns2:method id="getChangeField" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{changeLocator}/parent-changes"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="changeLocator" style="template" type="xs:string"/><ns2:method id="getParentChanges" name="GET"><ns2:response><ns2:representation element="changes" mediaType="application/xml"/><ns2:representation element="changes" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{changeLocator}/parent-revisions"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="changeLocator" style="template" type="xs:string"/><ns2:method id="getChangeParentRevisions" name="GET"><ns2:doc>Experimental support only!</ns2:doc><ns2:response><ns2:representation element="items" mediaType="application/xml"/><ns2:representation element="items" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{changeLocator}/vcs-root"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="changeLocator" style="template" type="xs:string"/><ns2:method id="getChangeVCSRoot" name="GET"><ns2:doc>Experimental support only!</ns2:doc><ns2:response><ns2:representation element="vcs-root-instance-ref" mediaType="application/xml"/><ns2:representation element="vcs-root-instance-ref" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{changeLocator}/attributes"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="changeLocator" style="template" type="xs:string"/><ns2:method id="getChangeAttributes" name="GET"><ns2:doc>Experimental support only!</ns2:doc><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{changeLocator}/duplicates"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="changeLocator" style="template" type="xs:string"/><ns2:method id="getChangeDuplicates" name="GET"><ns2:doc>Experimental support only!</ns2:doc><ns2:response><ns2:representation element="changes" mediaType="application/xml"/><ns2:representation element="changes" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{changeLocator}/issues"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="changeLocator" style="template" type="xs:string"/><ns2:method id="getChangeIssue" name="GET"><ns2:doc>Experimental support only!</ns2:doc><ns2:response><ns2:representation element="issues" mediaType="application/xml"/><ns2:representation element="issues" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{changeLocator}/buildTypes"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="changeLocator" style="template" type="xs:string"/><ns2:method id="getRelatedBuildTypes" name="GET"><ns2:doc>Experimental support only!</ns2:doc><ns2:response><ns2:representation element="buildTypes" mediaType="application/xml"/><ns2:representation element="buildTypes" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{changeLocator}/firstBuilds"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="changeLocator" style="template" type="xs:string"/><ns2:method id="getChangeFirstBuilds" name="GET"><ns2:doc>Experimental support only!</ns2:doc><ns2:response><ns2:representation element="builds" mediaType="application/xml"/><ns2:representation element="builds" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{changeLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="changeLocator" style="template" type="xs:string"/><ns2:method id="serveChange" name="GET"><ns2:response><ns2:representation element="change" mediaType="application/xml"/><ns2:representation element="change" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource></ns2:resource><ns2:resource path="/app/rest/server"><ns2:method id="serveServerInfo" name="GET"><ns2:response><ns2:representation element="server" mediaType="application/xml"/><ns2:representation element="server" mediaType="application/json"/></ns2:response></ns2:method><ns2:resource path="/backup"><ns2:method id="startBackup" name="POST"><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="fileName" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="addTimestamp" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includeConfigs" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includeDatabase" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includeBuildLogs" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includePersonalChanges" style="query" type="xs:boolean"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="getBackupStatus" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/plugins"><ns2:method id="servePlugins" name="GET"><ns2:response><ns2:representation element="plugins" mediaType="application/xml"/><ns2:representation element="plugins" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:method id="serveServerVersion" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource></ns2:resource><ns2:resource path="/app/rest/buildTypes"><ns2:method id="serveBuildTypesXML" name="GET"><ns2:response><ns2:representation element="buildTypes" mediaType="application/xml"/><ns2:representation element="buildTypes" mediaType="application/json"/></ns2:response></ns2:method><ns2:resource path="/{btLocator}/features/{featureId}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="featureId" style="template" type="xs:string"/><ns2:method id="getFeature" name="GET"><ns2:response><ns2:representation element="feature" mediaType="application/xml"/><ns2:representation element="feature" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteFeature" name="DELETE"/></ns2:resource><ns2:resource path="/{btLocator}/artifact-dependencies/{artifactDepLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="artifactDepLocator" style="template" type="xs:string"/><ns2:method id="getArtifactDep" name="GET"><ns2:response><ns2:representation element="artifact-dependency" mediaType="application/xml"/><ns2:representation element="artifact-dependency" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteArtifactDep" name="DELETE"/></ns2:resource><ns2:resource path="/{btLocator}/agent-requirements/{agentRequirementLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="agentRequirementLocator" style="template" type="xs:string"/><ns2:method id="getAgentRequirement" name="GET"><ns2:response><ns2:representation element="agent-requirement" mediaType="application/xml"/><ns2:representation element="agent-requirement" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteAgentRequirement" name="DELETE"/></ns2:resource><ns2:resource path="/{btLocator}/steps"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="addStep" name="POST"><ns2:request><ns2:representation element="step" mediaType="application/xml"/><ns2:representation element="step" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="step" mediaType="application/xml"/><ns2:representation element="step" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="replaceSteps" name="PUT"><ns2:request><ns2:representation element="steps" mediaType="application/xml"/><ns2:representation element="steps" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="steps" mediaType="application/xml"/><ns2:representation element="steps" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="getSteps" name="GET"><ns2:response><ns2:representation element="steps" mediaType="application/xml"/><ns2:representation element="steps" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/features"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="getFeatures" name="GET"><ns2:response><ns2:representation element="features" mediaType="application/xml"/><ns2:representation element="features" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="addFeature" name="POST"><ns2:request><ns2:representation element="feature" mediaType="*/*"/></ns2:request><ns2:response><ns2:representation element="feature" mediaType="application/xml"/><ns2:representation element="feature" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="replaceFeatures" name="PUT"><ns2:request><ns2:representation element="features" mediaType="application/xml"/><ns2:representation element="features" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="features" mediaType="application/xml"/><ns2:representation element="features" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/investigations"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="getInvestigations" name="GET"><ns2:response><ns2:representation element="investigations" mediaType="application/xml"/><ns2:representation element="investigations" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/vcs-root-entries"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="getVcsRootEntries" name="GET"><ns2:response><ns2:representation element="vcs-root-entries" mediaType="application/xml"/><ns2:representation element="vcs-root-entries" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="replaceVcsRootEntries" name="PUT"><ns2:request><ns2:representation element="vcs-root-entries" mediaType="application/xml"/><ns2:representation element="vcs-root-entries" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="vcs-root-entries" mediaType="application/xml"/><ns2:representation element="vcs-root-entries" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="addVcsRootEntry" name="POST"><ns2:request><ns2:representation element="vcs-root-entry" mediaType="application/xml"/><ns2:representation element="vcs-root-entry" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="vcs-root-entry" mediaType="application/xml"/><ns2:representation element="vcs-root-entry" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/vcs-root-entries/{id}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="id" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="getVcsRootEntry" name="GET"><ns2:response><ns2:representation element="vcs-root-entry" mediaType="application/xml"/><ns2:representation element="vcs-root-entry" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteVcsRootEntry" name="DELETE"/></ns2:resource><ns2:resource path="/{btLocator}/snapshot-dependencies"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="replaceSnapshotDeps" name="PUT"><ns2:doc>Replaces snapshot dependency with those sent in request.</ns2:doc><ns2:request><ns2:representation element="snapshot-dependencies" mediaType="application/xml"/><ns2:representation element="snapshot-dependencies" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="snapshot-dependencies" mediaType="application/xml"/><ns2:representation element="snapshot-dependencies" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="addSnapshotDep" name="POST"><ns2:doc>Creates new snapshot dependency. 'id' attribute is ignored in the submitted descriptor.
+ Reports error if new dependency cannot be created (e.g. another dependency on the specified build configuration already exists).</ns2:doc><ns2:request><ns2:representation element="snapshot-dependency" mediaType="application/xml"/><ns2:representation element="snapshot-dependency" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="snapshot-dependency" mediaType="application/xml"/><ns2:representation element="snapshot-dependency" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="getSnapshotDeps" name="GET"><ns2:response><ns2:representation element="snapshot-dependencies" mediaType="application/xml"/><ns2:representation element="snapshot-dependencies" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/snapshot-dependencies/{snapshotDepLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="snapshotDepLocator" style="template" type="xs:string"/><ns2:method id="deleteSnapshotDep" name="DELETE"/><ns2:method id="getSnapshotDep" name="GET"><ns2:response><ns2:representation element="snapshot-dependency" mediaType="application/xml"/><ns2:representation element="snapshot-dependency" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/triggers"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="replaceTriggers" name="PUT"><ns2:doc>Replaces trigger with those sent inthe request.</ns2:doc><ns2:request><ns2:representation element="triggers" mediaType="application/xml"/><ns2:representation element="triggers" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="triggers" mediaType="application/xml"/><ns2:representation element="triggers" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="addTrigger" name="POST"><ns2:doc>Creates new trigger. 'id' attribute is ignored in the submitted descriptor.
+ Reports error if new trigger cannot be created (e.g. only single trigger of the type is allowed for a build configuration).</ns2:doc><ns2:request><ns2:representation element="trigger" mediaType="application/xml"/><ns2:representation element="trigger" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="trigger" mediaType="application/xml"/><ns2:representation element="trigger" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="getTriggers" name="GET"><ns2:response><ns2:representation element="triggers" mediaType="application/xml"/><ns2:representation element="triggers" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/triggers/{triggerLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="triggerLocator" style="template" type="xs:string"/><ns2:method id="deleteTrigger" name="DELETE"/><ns2:method id="getTrigger" name="GET"><ns2:response><ns2:representation element="trigger" mediaType="application/xml"/><ns2:representation element="trigger" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/triggers/{triggerLocator}/{fieldName}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="triggerLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="fieldName" style="template" type="xs:string"/><ns2:method id="getTriggerSetting" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="changeTriggerSetting" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/agent-requirements"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="replaceAgentRequirements" name="PUT"><ns2:doc>Replaces agent requirements with those sent in the request.</ns2:doc><ns2:request><ns2:representation element="agent-requirements" mediaType="application/xml"/><ns2:representation element="agent-requirements" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="agent-requirements" mediaType="application/xml"/><ns2:representation element="agent-requirements" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="addAgentRequirement" name="POST"><ns2:doc>Creates new agent requirement. 'id' attribute is ignored in the submitted descriptor.
+ Reports error if new requirement cannot be created (e.g. another requirement is present for the parameter).</ns2:doc><ns2:request><ns2:representation element="agent-requirement" mediaType="application/xml"/><ns2:representation element="agent-requirement" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="agent-requirement" mediaType="application/xml"/><ns2:representation element="agent-requirement" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="getAgentRequirements" name="GET"><ns2:response><ns2:representation element="agent-requirements" mediaType="application/xml"/><ns2:representation element="agent-requirements" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/vcs-root-instances"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="getCurrentVcsInstances" name="GET"><ns2:response><ns2:representation element="vcs-root-instances" mediaType="application/xml"/><ns2:representation element="vcs-root-instances" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/builds/{buildLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildWithProject" name="GET"><ns2:response><ns2:representation element="build" mediaType="application/xml"/><ns2:representation element="build" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/builds/{buildLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildField" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/branches"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBranches" name="GET"><ns2:response><ns2:representation element="branches" mediaType="application/xml"/><ns2:representation element="branches" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/vcs-labeling"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="getVCSLabelingOptions" name="GET"><ns2:response><ns2:representation element="vcs-labeling" mediaType="application/xml"/><ns2:representation element="vcs-labeling" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="setVCSLabelingOptions" name="PUT"><ns2:request><ns2:representation element="vcs-labeling" mediaType="application/xml"/><ns2:representation element="vcs-labeling" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="vcs-labeling" mediaType="application/xml"/><ns2:representation element="vcs-labeling" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/newBuildTypeDescription"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="getExampleNewProjectDescription" name="GET"><ns2:doc>Experimental support only.
+ Use this to get an example of the bean to be posted to the /buildTypes request to create a new build type</ns2:doc><ns2:response><ns2:representation element="newBuildTypeDescription" mediaType="application/xml"/><ns2:representation element="newBuildTypeDescription" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/builds"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuilds" name="GET"><ns2:doc>Serves builds matching supplied condition.</ns2:doc><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="status" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="triggeredByUser" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includePersonal" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includeCanceled" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="onlyPinned" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="tag" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="agentName" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="sinceBuild" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="sinceDate" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="start" style="query" type="xs:long"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="count" style="query" type="xs:int"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="locator" style="query" type="xs:string"/></ns2:request><ns2:response><ns2:representation element="builds" mediaType="application/xml"/><ns2:representation element="builds" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/steps/{stepId}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="stepId" style="template" type="xs:string"/><ns2:method id="deleteStep" name="DELETE"/><ns2:method id="getStep" name="GET"><ns2:response><ns2:representation element="step" mediaType="application/xml"/><ns2:representation element="step" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuildTypeXML" name="GET"><ns2:doc>Serves build configuration or templates according to the locator.</ns2:doc><ns2:response><ns2:representation element="buildType" mediaType="application/xml"/><ns2:representation element="buildType" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteBuildType" name="DELETE"/></ns2:resource><ns2:resource path="/{btLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuildTypeField" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="setBuildTypeField" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/buildTags"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuildTypeBuildsTags" name="GET"><ns2:response><ns2:representation element="tags" mediaType="application/xml"/><ns2:representation element="tags" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/parameters"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuildTypeParameters" name="GET"><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="locator" style="query" type="xs:string"/></ns2:request><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="changeBuildTypeParameters" name="PUT"><ns2:request><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteAllBuildTypeParameters" name="DELETE"/></ns2:resource><ns2:resource path="/{btLocator}/parameters/{name}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="name" style="template" type="xs:string"/><ns2:method id="serveBuildTypeParameter" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="putBuildTypeParameter" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="deleteBuildTypeParameter" name="DELETE"/></ns2:resource><ns2:resource path="/{btLocator}/settings/{name}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="name" style="template" type="xs:string"/><ns2:method id="serveBuildTypeSettings" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="putBuildTypeSetting" name="PUT"><ns2:request><ns2:representation mediaType="*/*"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/settings"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuildTypeSettings" name="GET"><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="replaceBuildTypeSettings" name="PUT"><ns2:request><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/template"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="serveBuildTypeTemplate" name="GET"><ns2:response><ns2:representation element="buildType-ref" mediaType="application/xml"/><ns2:representation element="buildType-ref" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="getTemplateAssociation" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation element="buildType-ref" mediaType="application/xml"/><ns2:representation element="buildType-ref" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteTemplateAssociation" name="DELETE"/></ns2:resource><ns2:resource path="/{btLocator}/steps/{stepId}/parameters"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="stepId" style="template" type="xs:string"/><ns2:method id="getStepParameters" name="GET"><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="replaceStepParameters" name="PUT"><ns2:request><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/steps/{stepId}/parameters/{parameterName}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="stepId" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="parameterName" style="template" type="xs:string"/><ns2:method id="getStepParameter" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="addStepParameter" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/steps/{stepId}/{fieldName}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="fieldName" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="stepId" style="template" type="xs:string"/><ns2:method id="getStepSetting" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="changeStepSetting" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/features/{featureId}/parameters"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="featureId" style="template" type="xs:string"/><ns2:method id="getFeatureParameters" name="GET"><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="replaceFeatureParameters" name="PUT"><ns2:request><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/features/{featureId}/parameters/{parameterName}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="featureId" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="parameterName" style="template" type="xs:string"/><ns2:method id="getFeatureParameter" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="addFeatureParameter" name="PUT"><ns2:request><ns2:representation mediaType="*/*"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/features/{featureId}/{name}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="name" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="featureId" style="template" type="xs:string"/><ns2:method id="getFeatureSetting" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="changeFeatureSetting" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{btLocator}/artifact-dependencies"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:method id="getArtifactDeps" name="GET"><ns2:response><ns2:representation element="artifact-dependencies" mediaType="application/xml"/><ns2:representation element="artifact-dependencies" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="replaceArtifactDeps" name="PUT"><ns2:doc>Replaces the dependencies to those sent in the request.</ns2:doc><ns2:request><ns2:representation element="artifact-dependencies" mediaType="application/xml"/><ns2:representation element="artifact-dependencies" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="artifact-dependencies" mediaType="application/xml"/><ns2:representation element="artifact-dependencies" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="addArtifactDep" name="POST"><ns2:request><ns2:representation element="artifact-dependency" mediaType="*/*"/></ns2:request><ns2:response><ns2:representation element="artifact-dependency" mediaType="application/xml"/><ns2:representation element="artifact-dependency" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource></ns2:resource><ns2:resource path="/app/rest/userGroups"><ns2:method id="addGroup" name="POST"><ns2:request><ns2:representation element="group" mediaType="application/xml"/><ns2:representation element="group" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="group" mediaType="application/xml"/><ns2:representation element="group" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="serveGroups" name="GET"><ns2:response><ns2:representation element="groups" mediaType="application/xml"/><ns2:representation element="groups" mediaType="application/json"/></ns2:response></ns2:method><ns2:resource path="/{groupLocator}/roles"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="groupLocator" style="template" type="xs:string"/><ns2:method id="addRole" name="POST"><ns2:request><ns2:representation element="role" mediaType="application/xml"/><ns2:representation element="role" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="role" mediaType="application/xml"/><ns2:representation element="role" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="listRoles" name="GET"><ns2:response><ns2:representation element="roles" mediaType="application/xml"/><ns2:representation element="roles" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="addRolePut" name="PUT"><ns2:request><ns2:representation element="roles" mediaType="application/xml"/><ns2:representation element="roles" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="roles" mediaType="application/xml"/><ns2:representation element="roles" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{groupLocator}/roles/{roleId}/{scope}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="groupLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="scope" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="roleId" style="template" type="xs:string"/><ns2:method id="addRoleSimple" name="POST"><ns2:response><ns2:representation element="role" mediaType="application/xml"/><ns2:representation element="role" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteRole" name="DELETE"/><ns2:method id="listRole" name="GET"><ns2:response><ns2:representation element="role" mediaType="application/xml"/><ns2:representation element="role" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{groupLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="groupLocator" style="template" type="xs:string"/><ns2:method id="deleteGroup" name="DELETE"/><ns2:method id="serveGroup" name="GET"><ns2:response><ns2:representation element="group" mediaType="application/xml"/><ns2:representation element="group" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource></ns2:resource><ns2:resource path="/app/rest/debug"><ns2:doc>Provides some debug abilities for the server. Experimental only. Should be used with caution or better not used if not advised by JetBrains
+ These should never be used for non-debug purposes and the API here can change in future versions of TeamCity without any notice.</ns2:doc><ns2:resource path="/database/query/{query}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="query" style="template" type="xs:string"/><ns2:method id="executeDBQuery" name="GET"><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="fieldDelimiter" style="query" type="xs:string" default=", "/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="count" style="query" type="xs:int" default="1000"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain; charset=UTF-8"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/vcsCheckingForChangesQueue"><ns2:method id="scheduleCheckingForChanges" name="POST"><ns2:doc>Experimental use only!</ns2:doc><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="locator" style="query" type="xs:string"/></ns2:request><ns2:response><ns2:representation element="vcs-root-instances" mediaType="application/xml"/><ns2:representation element="vcs-root-instances" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/database/tables"><ns2:method id="listDBTables" name="GET"><ns2:response><ns2:representation mediaType="text/plain; charset=UTF-8"/></ns2:response></ns2:method></ns2:resource></ns2:resource><ns2:resource path="/app/rest/builds"><ns2:method id="serveAllBuilds" name="GET"><ns2:doc>Serves builds matching supplied condition.</ns2:doc><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildType" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="status" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="triggeredByUser" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includePersonal" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includeCanceled" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="onlyPinned" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="tag" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="agentName" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="sinceBuild" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="sinceDate" style="query" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="start" style="query" type="xs:long"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="count" style="query" type="xs:int"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="locator" style="query" type="xs:string"/></ns2:request><ns2:response><ns2:representation element="builds" mediaType="application/xml"/><ns2:representation element="builds" mediaType="application/json"/></ns2:response></ns2:method><ns2:resource path="/{buildLocator}/resulting-properties/{propertyName}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="propertyName" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="getParameter" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/artifacts"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="getArtifacts" name="GET"><ns2:doc>More user-friendly URL for "/{buildLocator}/artifacts/children" one.</ns2:doc><ns2:response><ns2:representation element="files" mediaType="application/xml"/><ns2:representation element="files" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/artifacts/metadata{path:(/.*)?}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="path" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="getArtifactMetadata" name="GET"><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="resolveParameters" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="locator" style="query" type="xs:string"/></ns2:request><ns2:response><ns2:representation element="file" mediaType="application/xml"/><ns2:representation element="file" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="deleteBuild" name="DELETE"/><ns2:method id="serveBuild" name="GET"><ns2:response><ns2:representation element="build" mediaType="application/xml"/><ns2:representation element="build" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/pin/"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="pinBuild" name="PUT"><ns2:doc>Pins a build</ns2:doc><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request></ns2:method><ns2:method id="getPinned" name="GET"><ns2:doc>Fetches current build pinned status.</ns2:doc><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="unpinBuild" name="DELETE"><ns2:doc>Unpins a build</ns2:doc><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/tags/"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveTags" name="GET"><ns2:response><ns2:representation element="tags" mediaType="application/xml"/><ns2:representation element="tags" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="addTags" name="POST"><ns2:doc>Adds a set of tags to a build</ns2:doc><ns2:request><ns2:representation element="tags" mediaType="application/xml"/><ns2:representation element="tags" mediaType="application/json"/></ns2:request></ns2:method><ns2:method id="addTag" name="POST"><ns2:doc>Adds a single tag to a build</ns2:doc><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="replaceTags" name="PUT"><ns2:doc>Replaces build's tags.</ns2:doc><ns2:request><ns2:representation element="tags" mediaType="application/xml"/><ns2:representation element="tags" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="tags" mediaType="application/xml"/><ns2:representation element="tags" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/resulting-properties/"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildActualParameters" name="GET"><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/artifacts/children{path:(/.*)?}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="path" style="template" type="xs:string" default=""/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="getArtifactChildren" name="GET"><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="resolveParameters" style="query" type="xs:boolean"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="locator" style="query" type="xs:string"/></ns2:request><ns2:response><ns2:representation element="files" mediaType="application/xml"/><ns2:representation element="files" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/artifacts/content{path:(/.*)?}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="path" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="getArtifactContent" name="GET"><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="resolveParameters" style="query" type="xs:boolean"/></ns2:request><ns2:response><ns2:representation mediaType="*/*"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/artifacts/files{path:(/.*)?}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="path" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="getArtifactFilesContent" name="GET"><ns2:response><ns2:representation mediaType="*/*"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/sources/files/{fileName:.+}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="fileName" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveSourceFile" name="GET"><ns2:response><ns2:representation mediaType="application/octet-stream"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/related-issues"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildRelatedIssuesOld" name="GET"><ns2:response><ns2:representation element="issuesUsages" mediaType="application/xml"/><ns2:representation element="issuesUsages" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/relatedIssues"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildRelatedIssues" name="GET"><ns2:response><ns2:representation element="issuesUsages" mediaType="application/xml"/><ns2:representation element="issuesUsages" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildFieldByBuildOnly" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/statistics/"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildStatisticValues" name="GET"><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/statistics/{name}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="name" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildStatisticValue" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{buildLocator}/comment"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="replaceComment" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request></ns2:method><ns2:method id="deleteComment" name="DELETE"/></ns2:resource><ns2:resource path="/{buildLocator}/statusIcon"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildStatusIcon" name="GET"><ns2:response><ns2:representation mediaType="*/*"/></ns2:response></ns2:method></ns2:resource></ns2:resource><ns2:resource path="/app/rest/cctray"><ns2:resource path="/projects.xml"><ns2:method id="serveProjects" name="GET"><ns2:response><ns2:representation element="Projects" mediaType="application/xml"/><ns2:representation element="Projects" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource></ns2:resource><ns2:resource path="/app/rest/vcs-roots"><ns2:method id="addRoot" name="POST"><ns2:request><ns2:representation element="vcs-root" mediaType="application/xml"/><ns2:representation element="vcs-root" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="vcs-root" mediaType="application/xml"/><ns2:representation element="vcs-root" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="serveRoots" name="GET"><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="locator" style="query" type="xs:string"/></ns2:request><ns2:response><ns2:representation element="vcs-roots" mediaType="application/xml"/><ns2:representation element="vcs-roots" mediaType="application/json"/></ns2:response></ns2:method><ns2:resource path="/{vcsRootLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootLocator" style="template" type="xs:string"/><ns2:method id="setField" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="serveField" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{vcsRootLocator}/properties/{name}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="name" style="template" type="xs:string"/><ns2:method id="putParameter" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="serveProperty" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="deleteParameter" name="DELETE"/></ns2:resource><ns2:resource path="/{vcsRootLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootLocator" style="template" type="xs:string"/><ns2:method id="serveRoot" name="GET"><ns2:response><ns2:representation element="vcs-root" mediaType="application/xml"/><ns2:representation element="vcs-root" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteRoot" name="DELETE"/></ns2:resource><ns2:resource path="/{vcsRootLocator}/instances/{vcsRootInstanceLocator}/properties"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootInstanceLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootLocator" style="template" type="xs:string"/><ns2:method id="serveRootInstanceProperties" name="GET"><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{vcsRootLocator}/instances/{vcsRootInstanceLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootInstanceLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootLocator" style="template" type="xs:string"/><ns2:method id="serveInstanceField" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="setInstanceField" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{vcsRootLocator}/instances"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootLocator" style="template" type="xs:string"/><ns2:method id="serveRootInstances" name="GET"><ns2:response><ns2:representation element="vcs-root-instances" mediaType="application/xml"/><ns2:representation element="vcs-root-instances" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{vcsRootLocator}/instances/{vcsRootInstanceLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootInstanceLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootLocator" style="template" type="xs:string"><ns2:doc>this is effectively ignored as vcsRootInstanceLocator should specify instance fully</ns2:doc></ns2:param><ns2:method id="serveRootInstance" name="GET"><ns2:response><ns2:representation element="vcs-root-instance" mediaType="application/xml"/><ns2:representation element="vcs-root-instance" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{vcsRootLocator}/properties"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="vcsRootLocator" style="template" type="xs:string"/><ns2:method id="serveProperties" name="GET"><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="changProperties" name="PUT"><ns2:request><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:request><ns2:response><ns2:representation element="properties" mediaType="application/xml"/><ns2:representation element="properties" mediaType="application/json"/></ns2:response></ns2:method><ns2:method id="deleteAllProperties" name="DELETE"/></ns2:resource></ns2:resource><ns2:resource path="/app/rest"><ns2:method id="serveRoot" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:resource path="/version"><ns2:method id="serveApiVersion" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/info"><ns2:method id="servePluginInfo" name="GET"><ns2:response><ns2:representation element="plugin" mediaType="application/xml"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{projectLocator}/{btLocator}/{buildLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="projectLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="btLocator" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="buildLocator" style="template" type="xs:string"/><ns2:method id="serveBuildFieldShort" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource></ns2:resource><ns2:resource path="/app/rest/agents"><ns2:method id="serveAgents" name="GET"><ns2:request><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includeDisconnected" style="query" type="xs:boolean" default="true"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="includeUnauthorized" style="query" type="xs:boolean" default="true"/></ns2:request><ns2:response><ns2:representation element="agents-ref" mediaType="application/xml"/><ns2:representation element="agents-ref" mediaType="application/json"/></ns2:response></ns2:method><ns2:resource path="/{agentLocator}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="agentLocator" style="template" type="xs:string"/><ns2:method id="serveAgent" name="GET"><ns2:response><ns2:representation element="agent" mediaType="application/xml"/><ns2:representation element="agent" mediaType="application/json"/></ns2:response></ns2:method></ns2:resource><ns2:resource path="/{agentLocator}/{field}"><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="field" style="template" type="xs:string"/><ns2:param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="agentLocator" style="template" type="xs:string"/><ns2:method id="serveAgentField" name="GET"><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method><ns2:method id="setAgentField" name="PUT"><ns2:request><ns2:representation mediaType="text/plain"/></ns2:request><ns2:response><ns2:representation mediaType="text/plain"/></ns2:response></ns2:method></ns2:resource></ns2:resource></ns2:resources></ns2:application>
\ No newline at end of file
diff --git a/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/storage.version b/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/storage.version
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/parentFiles/parentDir/storage.version
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git a/ttorrent-master/tests/src/test/resources/torrents/file1.jar.torrent b/ttorrent-master/tests/src/test/resources/torrents/file1.jar.torrent
new file mode 100644
index 0000000..e169744
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/torrents/file1.jar.torrent
Binary files differ
diff --git a/ttorrent-master/tests/src/test/resources/torrents/file2.jar.torrent b/ttorrent-master/tests/src/test/resources/torrents/file2.jar.torrent
new file mode 100644
index 0000000..85d3a3a
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/torrents/file2.jar.torrent
Binary files differ
diff --git a/ttorrent-master/tests/src/test/resources/torrents/parentDir.linux.torrent b/ttorrent-master/tests/src/test/resources/torrents/parentDir.linux.torrent
new file mode 100644
index 0000000..fa4ce56
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/torrents/parentDir.linux.torrent
@@ -0,0 +1 @@
+d8:announce30:http://localhost:6969/announce10:created by5:Test213:creation datei1376051000e4:infod5:filesld6:lengthi252788e4:pathl17:AccuRevCommon.jareed6:lengthi188910e4:pathl23:commons-io-cio2.5_3.jareed6:lengthi240e4:pathl28:commons-io-cio2.5_3.jar.linkeed6:lengthi82297e4:pathl5:inDir16:application.wadleed6:lengthi1e4:pathl15:storage.versioneee4:name9:parentDir12:piece lengthi524288e6:pieces20:NÜJ¾MØ=û¡Ñ]vb¦£6ee
\ No newline at end of file
diff --git a/ttorrent-master/tests/src/test/resources/torrents/parentDir.win.torrent b/ttorrent-master/tests/src/test/resources/torrents/parentDir.win.torrent
new file mode 100644
index 0000000..210292d
--- /dev/null
+++ b/ttorrent-master/tests/src/test/resources/torrents/parentDir.win.torrent
@@ -0,0 +1 @@
+d8:announce30:http://localhost:6969/announce10:created by5:Test213:creation datei1376051000e4:infod5:filesld6:lengthi252788e4:pathl17:AccuRevCommon.jareed6:lengthi188910e4:pathl23:commons-io-cio2.5_3.jareed6:lengthi241e4:pathl28:commons-io-cio2.5_3.jar.linkeed6:lengthi82305e4:pathl5:inDir16:application.wadleed6:lengthi1e4:pathl15:storage.versioneee4:name9:parentDir12:piece lengthi524288e6:pieces20:Tvhdé^ùÍv¤}ïÚKF±ee
\ No newline at end of file