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 &quot;locator&quot; 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 &quot;locator&quot; 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 &quot;locator&quot; 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 &quot;locator&quot; parameter instead. Note that corresponding locator dimension is &quot;vcsRootInstance&quot;</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 &quot;locator&quot; 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 &quot;locator&quot; 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 &quot;locator&quot; 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 &quot;/{buildLocator}/artifacts/children&quot; 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