diff --git a/Dockerfile b/Dockerfile index 9618b92..11995ed5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,5 +24,5 @@ COPY --from=build /artifact/joal.jar /joal/joal.jar VOLUME /data -ENTRYPOINT ["java","-jar","/joal/joal.jar"] +ENTRYPOINT ["java", "-jar", "/joal/joal.jar"] CMD ["--joal-conf=/data"] diff --git a/README.md b/README.md index ac597f6..00a002e 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ The application configuration belongs in `joal-conf/config.json`. | ![Edge][browser-edge] | ![no][support-no] | Lack of `referrer-policy` | | ![Internet explorer][browser-ie] | ![no][support-never] | Not enough space to explain... | -Some non-supported browser might works, but they may be unsafe due to the lack of support for `referrer-policy`. +Some non-supported browser might work, but they may be unsafe due to the lack of support for `referrer-policy`. ## Community projects diff --git a/src/main/java/org/araymond/joal/ApplicationClosingListener.java b/src/main/java/org/araymond/joal/ApplicationClosingListener.java index f719c55..e8399c2 100644 --- a/src/main/java/org/araymond/joal/ApplicationClosingListener.java +++ b/src/main/java/org/araymond/joal/ApplicationClosingListener.java @@ -28,10 +28,10 @@ public class ApplicationClosingListener implements ApplicationListener clientFiles = this.bitTorrentClientProvider.listClientFiles(); - this.publisher.publishEvent(new ListOfClientFilesEvent(clientFiles)); - this.bitTorrentClientProvider.generateNewClient(); - final BitTorrentClient bitTorrentClient = bitTorrentClientProvider.get(); + final AppConfiguration appConfig = this.configProvider.init(); + this.appEventPublisher.publishEvent(new ListOfClientFilesEvent(this.listClientFiles())); + final BitTorrentClient bitTorrentClient = bitTorrentClientProvider.generateNewClient(); - final RandomSpeedProvider randomSpeedProvider = new RandomSpeedProvider(appConfiguration); - this.bandwidthDispatcher = new BandwidthDispatcher(5000, randomSpeedProvider); - this.bandwidthDispatcher.setSpeedListener(new SeedManagerSpeedChangeListener(this.publisher)); + this.bandwidthDispatcher = new BandwidthDispatcher(5000, new RandomSpeedProvider(appConfig)); // TODO: move interval to config + this.bandwidthDispatcher.setSpeedListener(new SeedManagerSpeedChangeListener(this.appEventPublisher)); this.bandwidthDispatcher.start(); - final AnnounceDataAccessor announceDataAccessor = new AnnounceDataAccessor(bitTorrentClient, bandwidthDispatcher, this.connectionHandler); + final AnnounceDataAccessor announceDataAccessor = new AnnounceDataAccessor(bitTorrentClient, bandwidthDispatcher, connectionHandler); this.client = ClientBuilder.builder() - .withAppConfiguration(appConfiguration) + .withAppConfiguration(appConfig) .withTorrentFileProvider(this.torrentFileProvider) .withBandwidthDispatcher(this.bandwidthDispatcher) .withAnnouncerFactory(new AnnouncerFactory(announceDataAccessor, httpClient)) - .withEventPublisher(this.publisher) + .withEventPublisher(this.appEventPublisher) .withDelayQueue(new DelayQueue<>()) .build(); this.client.start(); - publisher.publishEvent(new GlobalSeedStartedEvent(bitTorrentClient)); + appEventPublisher.publishEvent(new GlobalSeedStartedEvent(bitTorrentClient)); } public void saveNewConfiguration(final AppConfiguration config) { @@ -135,16 +147,15 @@ public class SeedManager { public void saveTorrentToDisk(final String name, final byte[] bytes) { try { - // test if torrent file is valid or not. - MockedTorrent.fromBytes(bytes); + MockedTorrent.fromBytes(bytes); // test if torrent file is valid or not final String torrentName = name.endsWith(".torrent") ? name : name + ".torrent"; - Files.write(this.joalFoldersPath.getTorrentFilesPath().resolve(torrentName), bytes, StandardOpenOption.CREATE); + Files.write(this.joalFoldersPath.getTorrentsDirPath().resolve(torrentName), bytes, StandardOpenOption.CREATE); } catch (final Exception e) { log.warn("Failed to save torrent file", e); // If NullPointerException occurs (when the file is an empty file) there is no message. - final String errorMessage = Optional.ofNullable(e.getMessage()).orElse("Empty file"); - this.publisher.publishEvent(new FailedToAddTorrentFileEvent(name, errorMessage)); + final String errorMessage = firstNonNull(e.getMessage(), "Empty/bad file"); + this.appEventPublisher.publishEvent(new FailedToAddTorrentFileEvent(name, errorMessage)); } } @@ -160,29 +171,28 @@ public class SeedManager { return bitTorrentClientProvider.listClientFiles(); } - public List getCurrentlySeedingAnnouncer() { - return this.client == null ? new ArrayList<>() : client.getCurrentlySeedingAnnouncer(); + public List getCurrentlySeedingAnnouncers() { + return this.client == null ? emptyList() : client.getCurrentlySeedingAnnouncers(); } public Map getSpeedMap() { - return this.bandwidthDispatcher == null ? new HashMap<>() : bandwidthDispatcher.getSpeedMap(); + return this.bandwidthDispatcher == null ? emptyMap() : bandwidthDispatcher.getSpeedMap(); } public AppConfiguration getCurrentConfig() { try { return this.configProvider.get(); } catch (final IllegalStateException e) { - this.configProvider.init(); - return this.configProvider.get(); + return this.configProvider.init(); } } public String getCurrentEmulatedClient() { try { return this.bitTorrentClientProvider.get().getHeaders().stream() - .filter(entry -> "User-Agent".equalsIgnoreCase(entry.getKey())) - .map(Map.Entry::getValue) + .filter(hdr -> USER_AGENT.equalsIgnoreCase(hdr.getKey())) .findFirst() + .map(Map.Entry::getValue) .orElse("Unknown"); } catch (final IllegalStateException e) { return "None"; @@ -193,7 +203,7 @@ public class SeedManager { this.seeding = false; if (client != null) { this.client.stop(); - this.publisher.publishEvent(new GlobalSeedStoppedEvent()); + this.appEventPublisher.publishEvent(new GlobalSeedStoppedEvent()); this.client = null; } if (this.bandwidthDispatcher != null) { @@ -204,41 +214,47 @@ public class SeedManager { } + /** + * Contains the references to all the directories + * containing settings/configurations/torrent sources + * for JOAL. + */ + // TODO: move to config, also rename? @Getter public static class JoalFoldersPath { - private final Path confPath; - private final Path torrentFilesPath; - private final Path torrentArchivedPath; - private final Path clientsFilesPath; + private final Path confDirRootPath; // all other directories stem from this + private final Path torrentsDirPath; + private final Path torrentArchiveDirPath; + private final Path clientFilesDirPath; /** * Resolves, stores & exposes location to various configuration file-paths. */ - public JoalFoldersPath(final Path confPath) { - this.confPath = confPath; - this.torrentFilesPath = this.confPath.resolve("torrents"); - this.torrentArchivedPath = this.torrentFilesPath.resolve("archived"); - this.clientsFilesPath = this.confPath.resolve("clients"); + public JoalFoldersPath(final Path confDirRootPath) { + this.confDirRootPath = confDirRootPath; + this.torrentsDirPath = this.confDirRootPath.resolve("torrents"); + this.torrentArchiveDirPath = this.torrentsDirPath.resolve("archived"); + this.clientFilesDirPath = this.confDirRootPath.resolve("clients"); - if (!Files.isDirectory(confPath)) { - log.warn("No such directory: {}", this.confPath); + if (!isDirectory(confDirRootPath)) { + log.warn("No such directory: [{}]", this.confDirRootPath); } - if (!Files.isDirectory(torrentFilesPath)) { - log.warn("Sub-folder 'torrents' is missing in joal conf folder: {}", this.torrentFilesPath); + if (!isDirectory(torrentsDirPath)) { + log.warn("Sub-folder 'torrents' is missing in joal conf folder: [{}]", this.torrentsDirPath); } - if (!Files.isDirectory(clientsFilesPath)) { - log.warn("Sub-folder 'clients' is missing in joal conf folder: {}", this.clientsFilesPath); + if (!isDirectory(clientFilesDirPath)) { + log.warn("Sub-folder 'clients' is missing in joal conf folder: [{}]", this.clientFilesDirPath); } } } @RequiredArgsConstructor private static final class SeedManagerSpeedChangeListener implements SpeedChangedListener { - private final ApplicationEventPublisher publisher; + private final ApplicationEventPublisher appEventPublisher; @Override public void speedsHasChanged(final Map speeds) { - this.publisher.publishEvent(new SeedingSpeedsHasChangedEvent(speeds)); + this.appEventPublisher.publishEvent(new SeedingSpeedsHasChangedEvent(speeds)); } } } diff --git a/src/main/java/org/araymond/joal/core/bandwith/BandwidthDispatcher.java b/src/main/java/org/araymond/joal/core/bandwith/BandwidthDispatcher.java index 67c3163..8367198 100644 --- a/src/main/java/org/araymond/joal/core/bandwith/BandwidthDispatcher.java +++ b/src/main/java/org/araymond/joal/core/bandwith/BandwidthDispatcher.java @@ -1,11 +1,12 @@ package org.araymond.joal.core.bandwith; import com.google.common.annotations.VisibleForTesting; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FileUtils; import org.araymond.joal.core.bandwith.weight.PeersAwareWeightCalculator; import org.araymond.joal.core.bandwith.weight.WeightHolder; import org.araymond.joal.core.torrent.torrent.InfoHash; +import org.araymond.joal.core.ttorrent.client.announcer.response.BandwidthDispatcherNotifier; import java.util.HashMap; import java.util.HashSet; @@ -14,35 +15,36 @@ import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; import static java.util.Optional.ofNullable; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.apache.commons.io.FileUtils.byteCountToDisplaySize; import static org.apache.commons.lang3.ObjectUtils.getIfNull; +/** + * This class has 2 main functions: + *
    + *
  • listens for invocations from {@link BandwidthDispatcherNotifier} in order to register new torrents + * and update speeds based on the information received from tracker announce responses;
  • + *
  • periodically recomputes per-torrent speeds, and updates the tally in corresponding {@link TorrentSeedStats}
  • + *
+ */ @Slf4j +@RequiredArgsConstructor public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable { - private final ReentrantReadWriteLock lock; - private final WeightHolder weightHolder; - private final RandomSpeedProvider randomSpeedProvider; - private final Map torrentsSeedStats; - private final Map speedMap; + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final WeightHolder weightHolder = new WeightHolder<>(new PeersAwareWeightCalculator()); + private final Map torrentsSeedStats = new HashMap<>(); + private final Map speedMap = new HashMap<>(); private SpeedChangedListener speedChangedListener; - private final int threadPauseIntervalMs; private int threadLoopCounter; private volatile boolean stop; private Thread thread; + private final int threadPauseIntervalMs; + private final RandomSpeedProvider randomSpeedProvider; + private static final long TWENTY_MINS_MS = MINUTES.toMillis(20); - - public BandwidthDispatcher(final int threadPauseIntervalMs, final RandomSpeedProvider randomSpeedProvider) { - this.threadPauseIntervalMs = threadPauseIntervalMs; - this.torrentsSeedStats = new HashMap<>(); - this.speedMap = new HashMap<>(); - this.lock = new ReentrantReadWriteLock(); - - this.weightHolder = new WeightHolder<>(new PeersAwareWeightCalculator()); - this.randomSpeedProvider = randomSpeedProvider; - } - public void setSpeedListener(final SpeedChangedListener speedListener) { this.speedChangedListener = speedListener; } @@ -84,10 +86,10 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable public void run() { try { while (!this.stop) { - Thread.sleep(this.threadPauseIntervalMs); - ++this.threadLoopCounter; + MILLISECONDS.sleep(this.threadPauseIntervalMs); // refresh bandwidth every 20 minutes: + this.threadLoopCounter++; if (this.threadLoopCounter == TWENTY_MINS_MS / this.threadPauseIntervalMs) { this.refreshCurrentBandwidth(); this.threadLoopCounter = 0; @@ -97,17 +99,19 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable // to be scheduled as precise as we can. Locking too much will delay the Thread.sleep and cause stats // to be undervalued this.lock.readLock().lock(); - final Set> entrySet = new HashSet<>(this.torrentsSeedStats.entrySet()); + final Set> seedStatsView = new HashSet<>(this.torrentsSeedStats.entrySet()); this.lock.readLock().unlock(); - for (final Map.Entry entry : entrySet) { + // update the uploaded data amount tallies: + seedStatsView.forEach(entry -> { final long speedInBytesPerSecond = ofNullable(this.speedMap.get(entry.getKey())) .map(Speed::getBytesPerSecond) .orElse(0L); // Divide by 1000 because of the thread pause interval being in milliseconds // The multiplication HAS to be done before the division, otherwise we're going to have trailing zeroes - entry.getValue().addUploaded((speedInBytesPerSecond * this.threadPauseIntervalMs) / 1000); - } + // TODO: maybe instead of trusting threadPauseIntervalMs, use wall clock for calculations? + entry.getValue().addUploaded(speedInBytesPerSecond * this.threadPauseIntervalMs / 1000); + }); } } catch (final InterruptedException ignore) { } @@ -125,7 +129,7 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable } public void registerTorrent(final InfoHash infoHash) { - log.debug("{} has been added to bandwidth dispatcher.", infoHash.getHumanReadable()); + log.debug("{} has been added to bandwidth dispatcher", infoHash.getHumanReadable()); this.lock.writeLock().lock(); try { this.torrentsSeedStats.put(infoHash, new TorrentSeedStats()); @@ -136,7 +140,7 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable } public void unregisterTorrent(final InfoHash infoHash) { - log.debug("{} has been removed from bandwidth dispatcher.", infoHash.getHumanReadable()); + log.debug("{} has been removed from bandwidth dispatcher", infoHash.getHumanReadable()); this.lock.writeLock().lock(); try { this.weightHolder.remove(infoHash); @@ -156,58 +160,54 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable this.randomSpeedProvider.refresh(); this.recomputeSpeeds(); if (log.isDebugEnabled()) { - log.debug("Global bandwidth refreshed, new value is {}", FileUtils.byteCountToDisplaySize(this.randomSpeedProvider.getCurrentSpeed())); + log.debug("Global bandwidth refreshed, new value is {}/s", byteCountToDisplaySize(this.randomSpeedProvider.getCurrentSpeed())); } } finally { this.lock.writeLock().unlock(); } } + /** + * Update the values of the {@code speedMap}, introducing change to the current torrent speeds. + */ @VisibleForTesting void recomputeSpeeds() { log.debug("Refreshing all torrents speeds"); - for (final InfoHash infohash : this.torrentsSeedStats.keySet()) { - this.speedMap.compute(infohash, (hash, speed) -> { - if (speed == null) { - return new Speed(0); - } - double percentOfSpeedAssigned = this.weightHolder.getTotalWeight() == 0.0 - ? 0.0 - : this.weightHolder.getWeightFor(infohash) / this.weightHolder.getTotalWeight(); - speed.setBytesPerSecond((long) (this.randomSpeedProvider.getCurrentSpeed() * percentOfSpeedAssigned)); + this.torrentsSeedStats.keySet().forEach(infohash -> this.speedMap.compute(infohash, (hash, speed) -> { + if (speed == null) { // TODO: could it ever be null? both maps are updated under same lock anyway + return new Speed(0); + } + double percentOfSpeedAssigned = this.weightHolder.getTotalWeight() == 0.0 + ? 0.0 + : this.weightHolder.getWeightFor(infohash) / this.weightHolder.getTotalWeight(); + speed.setBytesPerSecond((long) (this.randomSpeedProvider.getCurrentSpeed() * percentOfSpeedAssigned)); - return speed; - }); - } + return speed; + })); if (speedChangedListener != null) { this.speedChangedListener.speedsHasChanged(new HashMap<>(this.speedMap)); } - try { - if (log.isDebugEnabled()) { - final StringBuilder sb = new StringBuilder("All torrents speeds has been refreshed:\n"); - final double totalWeight = this.weightHolder.getTotalWeight(); - this.speedMap.forEach((infoHash, speed) -> { - final String humanReadableSpeed = FileUtils.byteCountToDisplaySize(speed.getBytesPerSecond()); - final double torrentWeight = this.weightHolder.getWeightFor(infoHash); - final double weightInPercent = torrentWeight > 0.0 - ? totalWeight / torrentWeight * 100 - : 0; - sb.append(" ") - .append(infoHash.getHumanReadable()) - .append(":") - .append("\n ").append("current speed: ").append(humanReadableSpeed).append("/s") - .append("\n ").append("overall upload: ").append(FileUtils.byteCountToDisplaySize(this.torrentsSeedStats.get(infoHash).getUploaded())) - .append("\n ").append("weight: ").append(weightInPercent).append("% (").append(torrentWeight).append(" out of ").append(totalWeight).append(")") - .append("\n"); - }); - sb.setLength(sb.length() - 1); // remove last \n - log.debug(sb.toString()); - } - } catch (final Exception e) { - log.debug("Error while printing debug message for speed.", e); + if (log.isDebugEnabled() && !this.speedMap.isEmpty()) { + final StringBuilder sb = new StringBuilder("All torrents speeds have been refreshed:\n"); + final double totalWeight = this.weightHolder.getTotalWeight(); + this.speedMap.forEach((infoHash, speed) -> { + final String humanReadableSpeed = byteCountToDisplaySize(speed.getBytesPerSecond()); + final double torrentWeight = this.weightHolder.getWeightFor(infoHash); + final double weightInPercent = torrentWeight > 0.0 + ? totalWeight / torrentWeight * 100 + : 0; + sb.append(" ") + .append(infoHash.getHumanReadable()) + .append(":") + .append("\n ").append("current speed: ").append(humanReadableSpeed).append("/s") + .append("\n ").append("overall upload: ").append(byteCountToDisplaySize(this.torrentsSeedStats.get(infoHash).getUploaded())) + .append("\n ").append("weight: ").append(weightInPercent).append("% (").append(torrentWeight).append(" out of ").append(totalWeight).append(")") + .append("\n"); + }); + sb.setLength(sb.length() - 1); // remove last \n + log.debug(sb.toString()); } } - } diff --git a/src/main/java/org/araymond/joal/core/bandwith/Peers.java b/src/main/java/org/araymond/joal/core/bandwith/Peers.java index f6887c8..24e9817 100644 --- a/src/main/java/org/araymond/joal/core/bandwith/Peers.java +++ b/src/main/java/org/araymond/joal/core/bandwith/Peers.java @@ -13,8 +13,10 @@ public class Peers { public Peers(final int seeders, final int leechers) { this.seeders = seeders; this.leechers = leechers; - this.leechersRatio = (this.seeders + this.leechers) == 0 + + var allPeers = this.seeders + this.leechers; + this.leechersRatio = allPeers == 0 ? 0 - : ((float) this.leechers) / (this.seeders + this.leechers); + : ((float) this.leechers) / allPeers; } } diff --git a/src/main/java/org/araymond/joal/core/bandwith/RandomSpeedProvider.java b/src/main/java/org/araymond/joal/core/bandwith/RandomSpeedProvider.java index 1e0878f..38b9d58 100644 --- a/src/main/java/org/araymond/joal/core/bandwith/RandomSpeedProvider.java +++ b/src/main/java/org/araymond/joal/core/bandwith/RandomSpeedProvider.java @@ -6,19 +6,19 @@ import org.araymond.joal.core.config.AppConfiguration; import java.util.concurrent.ThreadLocalRandom; public class RandomSpeedProvider { - private final AppConfiguration appConfiguration; + private final AppConfiguration appConf; @Getter private long currentSpeed; // bytes/s - public RandomSpeedProvider(final AppConfiguration appConfiguration) { - this.appConfiguration = appConfiguration; + public RandomSpeedProvider(final AppConfiguration appConf) { + this.appConf = appConf; this.refresh(); } public void refresh() { - final long minUploadRateInBytes = appConfiguration.getMinUploadRate() * 1000L; - final long maxUploadRateInBytes = appConfiguration.getMaxUploadRate() * 1000L; + final long minUploadRateInBytes = appConf.getMinUploadRate() * 1000L; + final long maxUploadRateInBytes = appConf.getMaxUploadRate() * 1000L; this.currentSpeed = (minUploadRateInBytes == maxUploadRateInBytes) ? maxUploadRateInBytes : ThreadLocalRandom.current().nextLong(minUploadRateInBytes, maxUploadRateInBytes); diff --git a/src/main/java/org/araymond/joal/core/bandwith/TorrentSeedStats.java b/src/main/java/org/araymond/joal/core/bandwith/TorrentSeedStats.java index 6999526..e6ce4ce 100644 --- a/src/main/java/org/araymond/joal/core/bandwith/TorrentSeedStats.java +++ b/src/main/java/org/araymond/joal/core/bandwith/TorrentSeedStats.java @@ -2,11 +2,28 @@ package org.araymond.joal.core.bandwith; import lombok.Getter; +/** + * Holds the statistics for given torrent, to be announced/reported back to the tracker. + * + * For torrent protocol, see BitTorrent Tracker Protocol + */ @Getter -public class TorrentSeedStats { +public class TorrentSeedStats { // TODO: rename to TorrentSeedState? or TorrentState if/when we also start "downloading" + /** + * Total amount uploaded so far + */ private long uploaded; + /** + * Total amount downloaded so far + */ private long downloaded; + /** + * bytes this client still has to download. + * Note that this can't be computed from downloaded and the file length since the client might be + * resuming an earlier download, and there's a chance that some of the downloaded data failed an + * integrity check and had to be re-downloaded. + */ private long left; void addUploaded(final long bytes) { diff --git a/src/main/java/org/araymond/joal/core/bandwith/weight/PeersAwareWeightCalculator.java b/src/main/java/org/araymond/joal/core/bandwith/weight/PeersAwareWeightCalculator.java index edd9999..931d199 100644 --- a/src/main/java/org/araymond/joal/core/bandwith/weight/PeersAwareWeightCalculator.java +++ b/src/main/java/org/araymond/joal/core/bandwith/weight/PeersAwareWeightCalculator.java @@ -2,6 +2,9 @@ package org.araymond.joal.core.bandwith.weight; import org.araymond.joal.core.bandwith.Peers; +/** + * Allocates higher weights to torrents with more leechers. + */ public class PeersAwareWeightCalculator { public double calculate(final Peers peers) { final double leechersRatio = peers.getLeechersRatio(); diff --git a/src/main/java/org/araymond/joal/core/bandwith/weight/WeightHolder.java b/src/main/java/org/araymond/joal/core/bandwith/weight/WeightHolder.java index ea084c0..402e18f 100644 --- a/src/main/java/org/araymond/joal/core/bandwith/weight/WeightHolder.java +++ b/src/main/java/org/araymond/joal/core/bandwith/weight/WeightHolder.java @@ -1,6 +1,8 @@ package org.araymond.joal.core.bandwith.weight; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.araymond.joal.core.bandwith.BandwidthDispatcher; import org.araymond.joal.core.bandwith.Peers; import java.util.HashMap; @@ -10,20 +12,18 @@ import java.util.concurrent.locks.ReentrantLock; import static java.util.Optional.ofNullable; +/** + * Keeps track of the 'weights' of each and every torrent we're currently processing/uploading. + * These weights will be used (likely by {@link BandwidthDispatcher}) to calculate per-torrent + * speeds from our global configured bandwidth budget. + */ +@RequiredArgsConstructor public class WeightHolder { - private final Lock lock; + private final Lock lock = new ReentrantLock(); + private final Map weightMap = new HashMap<>(); private final PeersAwareWeightCalculator weightCalculator; - private final Map weightMap; - - @Getter - private double totalWeight; - - public WeightHolder(final PeersAwareWeightCalculator weightCalculator) { - this.weightCalculator = weightCalculator; - this.weightMap = new HashMap<>(); - this.lock = new ReentrantLock(); - } + @Getter private double totalWeight; public void addOrUpdate(final E item, final Peers peers) { final double weight = this.weightCalculator.calculate(peers); @@ -51,10 +51,9 @@ public class WeightHolder { * For performance reasons, this method does not benefit from the lock. * That's not a big deal because: * - if a value is not yet added it will return 0.0. - * - if a value is still present it will return the previous value. + * - if a value is present it will return the current value. */ public double getWeightFor(final E item) { - return ofNullable(weightMap.get(item)) - .orElse(0.0); + return weightMap.getOrDefault(item, 0.0); } } diff --git a/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClient.java b/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClient.java index 0c7cb96..3e6dcaa 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClient.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClient.java @@ -2,7 +2,6 @@ package org.araymond.joal.core.client.emulated; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -18,15 +17,26 @@ import org.araymond.joal.core.ttorrent.client.ConnectionHandler; import java.net.Inet4Address; import java.net.Inet6Address; +import java.net.InetAddress; import java.util.*; +import java.util.AbstractMap.SimpleImmutableEntry; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; +import static com.google.common.base.StandardSystemProperty.JAVA_VERSION; +import static com.google.common.base.StandardSystemProperty.OS_NAME; +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static java.lang.String.valueOf; import static java.util.Optional.ofNullable; +import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.araymond.joal.core.client.emulated.BitTorrentClientConfig.HttpHeader; /** + * This is our mocked torrent client that represents our specific configured client (eg 'deluge-2.0.3'). + *

+ * It's responsible for composing/formatting the request data sent to the trackers. It + * makes no requests itself. + *

* Created by raymo on 26/01/2017. */ @EqualsAndHashCode(exclude = "urlEncoder") @@ -35,10 +45,30 @@ public class BitTorrentClient { private final KeyGenerator keyGenerator; private final UrlEncoder urlEncoder; @Getter private final String query; - private final List> headers; + @Getter private final Set> headers; private final NumwantProvider numwantProvider; - BitTorrentClient(final PeerIdGenerator peerIdGenerator, final KeyGenerator keyGenerator, final UrlEncoder urlEncoder, final String query, final Collection headers, final NumwantProvider numwantProvider) { + private static final Pattern INFOHASH_PTRN = Pattern.compile("\\{infohash}"); + private static final Pattern UPLOADED_PTRN = Pattern.compile("\\{uploaded}"); + private static final Pattern DOWNLOADED_PTRN = Pattern.compile("\\{downloaded}"); + private static final Pattern LEFT_PTRN = Pattern.compile("\\{left}"); + private static final Pattern PORT_PTRN = Pattern.compile("\\{port}"); + private static final Pattern NUMWANT_PTRN = Pattern.compile("\\{numwant}"); + private static final Pattern PEER_ID_PTRN = Pattern.compile("\\{peerid}"); + private static final Pattern EVENT_PTRN = Pattern.compile("\\{event}"); + private static final Pattern KEY_PTRN = Pattern.compile("\\{key}"); + private static final Pattern JAVA_PTRN = Pattern.compile("\\{java}"); + private static final Pattern OS_PTRN = Pattern.compile("\\{os}"); + private static final Pattern LOCALE_PTRN = Pattern.compile("\\{locale}"); + private static final Pattern AMPERSAND_DUPES_PTRN = Pattern.compile("&{2,}"); + private static final Pattern IP_PTRN = Pattern.compile("\\{ip}"); + private static final Pattern IPV6_PTRN = Pattern.compile("\\{ipv6}"); + private static final Pattern IP_Q_PTRN = Pattern.compile("&*\\w+=\\{ip(?:v6)?}"); + private static final Pattern EVENT_Q_PTRN = Pattern.compile("&*\\w+=\\{event}"); + private static final Pattern PLACEHOLDER_PTRN = Pattern.compile("\\{.*?}"); + + BitTorrentClient(final PeerIdGenerator peerIdGenerator, final KeyGenerator keyGenerator, final UrlEncoder urlEncoder, + final String query, final Collection headers, final NumwantProvider numwantProvider) { Preconditions.checkNotNull(peerIdGenerator, "peerIdGenerator cannot be null or empty"); Preconditions.checkNotNull(urlEncoder, "urlEncoder cannot be null"); Preconditions.checkArgument(!StringUtils.isBlank(query), "query cannot be null or empty"); @@ -46,12 +76,13 @@ public class BitTorrentClient { Preconditions.checkNotNull(numwantProvider, "numwantProvider cannot be null"); this.peerIdGenerator = peerIdGenerator; this.urlEncoder = urlEncoder; - this.query = query; - this.headers = headers.stream().map(h -> new AbstractMap.SimpleImmutableEntry<>(h.getName(), h.getValue())).collect(Collectors.toList()); + this.query = AMPERSAND_DUPES_PTRN.matcher(query).replaceAll("&"); // collapse dupes; + this.headers = createRequestHeaders(headers); this.keyGenerator = keyGenerator; this.numwantProvider = numwantProvider; } + @VisibleForTesting public String getPeerId(final InfoHash infoHash, final RequestEvent event) { return peerIdGenerator.getPeerId(infoHash, event); } @@ -62,85 +93,77 @@ public class BitTorrentClient { .map(keyGen -> keyGen.getKey(infoHash, event)); } - public List> getHeaders() { - return ImmutableList.copyOf(headers); - } - @VisibleForTesting - Integer getNumwant(final RequestEvent event) { + int getNumwant(final RequestEvent event) { return this.numwantProvider.get(event); } - public String createRequestQuery(final RequestEvent event, final InfoHash torrentInfoHash, final TorrentSeedStats stats, final ConnectionHandler connectionHandler) { - String emulatedClientQuery = this.getQuery() - .replaceAll("\\{infohash}", urlEncoder.encode(torrentInfoHash.value())) - .replaceAll("\\{uploaded}", String.valueOf(stats.getUploaded())) - .replaceAll("\\{downloaded}", String.valueOf(stats.getDownloaded())) - .replaceAll("\\{left}", String.valueOf(stats.getLeft())) - .replaceAll("\\{port}", String.valueOf(connectionHandler.getPort())) - .replaceAll("\\{numwant}", String.valueOf(this.getNumwant(event))); + /** + * For torrent protocol, see https://wiki.theory.org/BitTorrent_Tracker_Protocol + */ + public String createRequestQuery(final RequestEvent event, final InfoHash torrentInfoHash, + final TorrentSeedStats stats, final ConnectionHandler connectionHandler) { + String q = INFOHASH_PTRN.matcher(this.getQuery()).replaceAll(urlEncoder.encode(torrentInfoHash.value())); + q = UPLOADED_PTRN.matcher(q).replaceAll(valueOf(stats.getUploaded())); + q = DOWNLOADED_PTRN.matcher(q).replaceAll(valueOf(stats.getDownloaded())); + q = LEFT_PTRN.matcher(q).replaceAll(valueOf(stats.getLeft())); + q = PORT_PTRN.matcher(q).replaceAll(valueOf(connectionHandler.getPort())); + q = NUMWANT_PTRN.matcher(q).replaceAll(valueOf(this.getNumwant(event))); final String peerId = this.peerIdGenerator.isShouldUrlEncode() ? urlEncoder.encode(this.getPeerId(torrentInfoHash, event)) : this.getPeerId(torrentInfoHash, event); - emulatedClientQuery = emulatedClientQuery.replaceAll("\\{peerid}", peerId); + q = PEER_ID_PTRN.matcher(q).replaceAll(peerId); // set ip or ipv6 then remove placeholders that were left empty - if (connectionHandler.getIpAddress() instanceof Inet4Address) { - emulatedClientQuery = emulatedClientQuery.replaceAll("\\{ip}", connectionHandler.getIpAddress().getHostAddress()); - } else if(connectionHandler.getIpAddress() instanceof Inet6Address) { - emulatedClientQuery = emulatedClientQuery.replaceAll("\\{ipv6}", urlEncoder.encode(connectionHandler.getIpAddress().getHostAddress())); + InetAddress addy = connectionHandler.getIpAddress(); + if (q.contains("{ip}") && addy instanceof Inet4Address) { + q = IP_PTRN.matcher(q).replaceAll(addy.getHostAddress()); + } else if (q.contains("{ipv6}") && addy instanceof Inet6Address) { + q = IPV6_PTRN.matcher(q).replaceAll(urlEncoder.encode(addy.getHostAddress())); } - emulatedClientQuery = emulatedClientQuery.replaceAll("[&]*[a-zA-Z0-9]+=\\{ipv6}", ""); - emulatedClientQuery = emulatedClientQuery.replaceAll("[&]*[a-zA-Z0-9]+=\\{ip}", ""); + q = IP_Q_PTRN.matcher(q).replaceAll(EMPTY); if (event == null || event == RequestEvent.NONE) { - // if event was NONE, remove the event from the query string - emulatedClientQuery = emulatedClientQuery.replaceAll("([&]*[a-zA-Z0-9]+=\\{event})", ""); + // if event was NONE, remove the event from the query string; this is the normal announce made at regular intervals. + q = EVENT_Q_PTRN.matcher(q).replaceAll(EMPTY); } else { - emulatedClientQuery = emulatedClientQuery.replaceAll("\\{event}", event.getEventName()); + q = EVENT_PTRN.matcher(q).replaceAll(event.getEventName()); } - if (emulatedClientQuery.contains("{key}")) { - final String key = this.getKey(torrentInfoHash, event).orElseThrow(() -> new IllegalStateException("Client request query contains 'key' but BitTorrentClient does not have a key.")); - emulatedClientQuery = emulatedClientQuery.replaceAll("\\{key}", urlEncoder.encode(key)); + if (q.contains("{key}")) { + final String key = this.getKey(torrentInfoHash, event) + .orElseThrow(() -> new IllegalStateException("Client request query contains 'key' but BitTorrentClient does not have a key")); + q = KEY_PTRN.matcher(q).replaceAll(urlEncoder.encode(key)); } - final Matcher matcher = Pattern.compile("(.*?\\{.*?})").matcher(emulatedClientQuery); - if (matcher.find()) { - final String unrecognizedPlaceHolder = matcher.group(); - throw new UnrecognizedClientPlaceholder("Placeholder " + unrecognizedPlaceHolder + " were not recognized while building announce URL."); + final Matcher placeholderMatcher = PLACEHOLDER_PTRN.matcher(q); + if (placeholderMatcher.find()) { + throw new UnrecognizedClientPlaceholder("Placeholder [" + placeholderMatcher.group() + "] were not recognized while building announce URL"); } - // we might have removed event, ipv6 or ipv6, some '&' might have remains, lets remove them. - if (emulatedClientQuery.endsWith("&")) { - emulatedClientQuery = emulatedClientQuery.substring(0, emulatedClientQuery.length() - 1); + if (q.endsWith("&")) { + q = q.substring(0, q.length() - 1); } - if (emulatedClientQuery.startsWith("&")) { - emulatedClientQuery = emulatedClientQuery.substring(1, emulatedClientQuery.length()); + if (q.startsWith("&")) { + q = q.substring(1); } - emulatedClientQuery = emulatedClientQuery.replaceAll("&{2,}", "&"); - return emulatedClientQuery; + + return q; } - public List> createRequestHeaders() { - final List> headers = new ArrayList<>(this.headers.size() + 1); + private Set> createRequestHeaders(Collection headers) { + return headers.stream().map(hdr -> { + String value = JAVA_PTRN.matcher(hdr.getValue()).replaceAll(System.getProperty(JAVA_VERSION.key())); + value = OS_PTRN.matcher(value).replaceAll(System.getProperty(OS_NAME.key())); + value = LOCALE_PTRN.matcher(value).replaceAll(Locale.getDefault().toLanguageTag()); - for (final Map.Entry header : this.headers) { - final String name = header.getKey(); - final String value = header.getValue() - .replaceAll("\\{java}", System.getProperty("java.version")) - .replaceAll("\\{os}", System.getProperty("os.name")) - .replaceAll("\\{locale}", Locale.getDefault().toLanguageTag()); - - final Matcher matcher = Pattern.compile("(\\{.*?})").matcher(value); - if (matcher.find()) { - final String unrecognizedPlaceHolder = matcher.group(); - throw new UnrecognizedClientPlaceholder("Placeholder " + unrecognizedPlaceHolder + " were not recognized while building client Headers."); + final Matcher placeholderMatcher = PLACEHOLDER_PTRN.matcher(value); + if (placeholderMatcher.find()) { + throw new UnrecognizedClientPlaceholder("Placeholder [" + placeholderMatcher.group() + "] were not recognized while building client Headers"); } - headers.add(new AbstractMap.SimpleImmutableEntry<>(name, value)); - } - return headers; + return new SimpleImmutableEntry<>(hdr.getName(), value); + }).collect(toImmutableSet()); } } diff --git a/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfig.java b/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfig.java index b3ca7fe..3b7c450 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfig.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfig.java @@ -17,20 +17,13 @@ import java.util.List; @EqualsAndHashCode @Getter public class BitTorrentClientConfig { - @JsonProperty("peerIdGenerator") private final PeerIdGenerator peerIdGenerator; - @JsonProperty("query") private final String query; - @JsonProperty("keyGenerator") private final KeyGenerator keyGenerator; - @JsonProperty("urlEncoder") private final UrlEncoder urlEncoder; - @JsonProperty("requestHeaders") private final List requestHeaders; - @JsonProperty("numwant") - private final Integer numwant; - @JsonProperty("numwantOnStop") - private final Integer numwantOnStop; + private final int numwant; + private final int numwantOnStop; @JsonCreator BitTorrentClientConfig( @@ -39,8 +32,8 @@ public class BitTorrentClientConfig { @JsonProperty(value = "keyGenerator") final KeyGenerator keyGenerator, @JsonProperty(value = "urlEncoder", required = true) final UrlEncoder urlEncoder, @JsonProperty(value = "requestHeaders", required = true) final List requestHeaders, - @JsonProperty(value = "numwant", required = true) final Integer numwant, - @JsonProperty(value = "numwantOnStop", required = true) final Integer numwantOnStop + @JsonProperty(value = "numwant", required = true) final int numwant, + @JsonProperty(value = "numwantOnStop", required = true) final int numwantOnStop ) { this.peerIdGenerator = peerIdGenerator; this.query = query; @@ -50,8 +43,8 @@ public class BitTorrentClientConfig { this.numwant = numwant; this.numwantOnStop = numwantOnStop; - if (this.query.contains("{key}") && this.keyGenerator == null) { - throw new TorrentClientConfigIntegrityException("Query string contains {key}, but no keyGenerator was found in .client file."); + if (this.keyGenerator == null && this.query.contains("{key}")) { + throw new TorrentClientConfigIntegrityException("Query string contains {key}, but no keyGenerator was found in .client file"); } } @@ -62,7 +55,8 @@ public class BitTorrentClientConfig { private final String value; @JsonCreator - HttpHeader(@JsonProperty(value = "name", required = true) final String name, @JsonProperty(value = "value", required = true) final String value) { + HttpHeader(@JsonProperty(value = "name", required = true) final String name, + @JsonProperty(value = "value", required = true) final String value) { Preconditions.checkNotNull(name); Preconditions.checkNotNull(value); this.name = name; diff --git a/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClientProvider.java b/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClientProvider.java index ff5461a..8d83aaf 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClientProvider.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/BitTorrentClientProvider.java @@ -17,12 +17,16 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Stream; +import static java.lang.Integer.parseInt; +import static java.lang.String.format; +import static java.nio.file.FileVisitOption.FOLLOW_LINKS; +import static java.nio.file.Files.isRegularFile; import static java.util.stream.Collectors.toList; /** * Provides as with an instance of {@link BitTorrentClient}, based on the * configured {@code client}. - * + *

* Created by raymo on 23/04/2017. */ @Slf4j @@ -32,20 +36,23 @@ public class BitTorrentClientProvider implements Provider { private final ObjectMapper objectMapper; private final Path clientsFolderPath; - public BitTorrentClientProvider(final JoalConfigProvider configProvider, final ObjectMapper objectMapper, final SeedManager.JoalFoldersPath joalFoldersPath) { + public BitTorrentClientProvider(final JoalConfigProvider configProvider, final ObjectMapper objectMapper, + final SeedManager.JoalFoldersPath joalFoldersPath) { this.configProvider = configProvider; this.objectMapper = objectMapper; - this.clientsFolderPath = joalFoldersPath.getClientsFilesPath(); + this.clientsFolderPath = joalFoldersPath.getClientFilesDirPath(); } public List listClientFiles() { - try (final Stream paths = Files.walk(this.clientsFolderPath)) { - return paths.filter(p -> p.toString().endsWith(".client")) + try (final Stream paths = Files.walk(this.clientsFolderPath, FOLLOW_LINKS)) { + return paths + .filter(Files::isRegularFile) .map(p -> p.getFileName().toString()) + .filter(name -> name.endsWith(".client")) .sorted(new SemanticVersionFilenameComparator()) .collect(toList()); } catch (final IOException e) { - throw new IllegalStateException("Failed to walk through .clients files", e); + throw new IllegalStateException("Failed to walk through .clients files in [" + this.clientsFolderPath + "]", e); } } @@ -61,17 +68,19 @@ public class BitTorrentClientProvider implements Provider { return bitTorrentClient; } - public void generateNewClient() throws FileNotFoundException, IllegalStateException { - log.debug("Generating new client."); + public BitTorrentClient generateNewClient() throws FileNotFoundException, IllegalStateException { + log.debug("Generating new client..."); final Path clientConfigPath = clientsFolderPath.resolve(configProvider.get().getClient()); - if (!Files.isRegularFile(clientConfigPath)) { - throw new FileNotFoundException(String.format("BitTorrent client configuration file [%s] not found", clientConfigPath.toAbsolutePath())); + if (!isRegularFile(clientConfigPath)) { + throw new FileNotFoundException(format("BitTorrent client configuration file [%s] not found", clientConfigPath.toAbsolutePath())); } try { BitTorrentClientConfig config = objectMapper.readValue(clientConfigPath.toFile(), BitTorrentClientConfig.class); this.bitTorrentClient = createClient(config); log.debug("New client successfully generated"); + + return this.bitTorrentClient; } catch (final IOException e) { throw new IllegalStateException(e); } @@ -91,8 +100,8 @@ public class BitTorrentClientProvider implements Provider { static final class SemanticVersionFilenameComparator implements Comparator { @Override - public final int compare(final String o1, final String o2) { - // remove file extension and replace '_' (which delimited build number with '.' + public int compare(final String o1, final String o2) { + // remove file extension and replace '_' (which delimited build number with '.') final String o1NameWithoutExtension = FilenameUtils.removeExtension(o1).replaceAll("_", "."); final String o2NameWithoutExtension = FilenameUtils.removeExtension(o2).replaceAll("_", "."); @@ -103,12 +112,16 @@ public class BitTorrentClientProvider implements Provider { return o1.compareTo(o2); } - final String[] o1VersionSufix = o1NameWithoutExtension.substring(o1NameWithoutExtension.lastIndexOf('-') + 1, o1NameWithoutExtension.length()).split("\\."); - final String[] o2VersionSufix = o2NameWithoutExtension.substring(o2NameWithoutExtension.lastIndexOf('-') + 1, o2NameWithoutExtension.length()).split("\\."); - final int length = Math.max(o1VersionSufix.length, o2VersionSufix.length); - for(int i = 0; i < length; i++) { - final int thisPart = i < o1VersionSufix.length ? Integer.parseInt(o1VersionSufix[i]) : 0; - final int thatPart = i < o2VersionSufix.length ? Integer.parseInt(o2VersionSufix[i]) : 0; + final String[] o1VersionSuffix = o1NameWithoutExtension + .substring(o1NameWithoutExtension.lastIndexOf('-') + 1) + .split("\\."); + final String[] o2VersionSuffix = o2NameWithoutExtension + .substring(o2NameWithoutExtension.lastIndexOf('-') + 1) + .split("\\."); + final int length = Math.max(o1VersionSuffix.length, o2VersionSuffix.length); + for (int i = 0; i < length; i++) { + final int thisPart = i < o1VersionSuffix.length ? parseInt(o1VersionSuffix[i]) : 0; + final int thatPart = i < o2VersionSuffix.length ? parseInt(o2VersionSuffix[i]) : 0; if(thisPart < thatPart) return -1; if(thisPart > thatPart) return 1; } diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/UrlEncoder.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/UrlEncoder.java index ac9abe7..9e8828f 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/UrlEncoder.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/UrlEncoder.java @@ -17,21 +17,19 @@ import static java.lang.String.valueOf; @Getter public class UrlEncoder { - @JsonProperty("encodingExclusionPattern") private final String encodingExclusionPattern; - @JsonProperty("encodedHexCase") private final Casing encodedHexCase; @JsonIgnore private final Pattern pattern; @JsonCreator public UrlEncoder( - @JsonProperty(value = "encodingExclusionPattern" ,required = true) final String encodingExclusionPattern, - @JsonProperty(value = "encodedHexCase" ,required = true) final Casing encodedHexCase + @JsonProperty(value = "encodingExclusionPattern", required = true) final String encodingExclusionPattern, + @JsonProperty(value = "encodedHexCase", required = true) final Casing encodedHexCase ) { this.encodingExclusionPattern = encodingExclusionPattern; - this.pattern = Pattern.compile(this.encodingExclusionPattern); this.encodedHexCase = encodedHexCase; + this.pattern = Pattern.compile(this.encodingExclusionPattern); } /** diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/KeyGenerator.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/KeyGenerator.java index fb5b2b8..5f466c5 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/KeyGenerator.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/KeyGenerator.java @@ -35,7 +35,7 @@ public abstract class KeyGenerator { protected KeyGenerator(final KeyAlgorithm keyAlgorithm, final Casing keyCase) { if (keyAlgorithm == null) { - throw new TorrentClientConfigIntegrityException("key algorithm must not be null."); + throw new TorrentClientConfigIntegrityException("key algorithm must not be null"); } this.algorithm = keyAlgorithm; this.keyCase = keyCase; @@ -45,9 +45,7 @@ public abstract class KeyGenerator { public abstract String getKey(final InfoHash infoHash, RequestEvent event); protected String generateKey() { - final String key = this.algorithm.generate(); - - return keyCase.toCase(key); + return keyCase.toCase(this.algorithm.generate()); } } diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/NeverRefreshKeyGenerator.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/NeverRefreshKeyGenerator.java index ac08eaf..e601436 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/NeverRefreshKeyGenerator.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/NeverRefreshKeyGenerator.java @@ -13,6 +13,7 @@ import org.araymond.joal.core.torrent.torrent.InfoHash; public class NeverRefreshKeyGenerator extends KeyGenerator { private final String key; + @JsonCreator NeverRefreshKeyGenerator( @JsonProperty(value = "algorithm", required = true) final KeyAlgorithm algorithm, diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TimedOrAfterStartedAnnounceRefreshKeyGenerator.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TimedOrAfterStartedAnnounceRefreshKeyGenerator.java index e5c90be..56f4284 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TimedOrAfterStartedAnnounceRefreshKeyGenerator.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TimedOrAfterStartedAnnounceRefreshKeyGenerator.java @@ -2,10 +2,7 @@ package org.araymond.joal.core.client.emulated.generator.key; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.annotations.VisibleForTesting; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; -import lombok.Getter; -import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException; import org.araymond.joal.core.client.emulated.generator.key.algorithm.KeyAlgorithm; import org.araymond.joal.core.client.emulated.utils.Casing; import org.araymond.joal.core.torrent.torrent.InfoHash; @@ -19,7 +16,7 @@ public class TimedOrAfterStartedAnnounceRefreshKeyGenerator extends TimedRefresh @JsonCreator TimedOrAfterStartedAnnounceRefreshKeyGenerator( - @JsonProperty(value = "refreshEvery", required = true) final Integer refreshEvery, + @JsonProperty(value = "refreshEvery", required = true) final int refreshEvery, @JsonProperty(value = "algorithm", required = true) final KeyAlgorithm algorithm, @JsonProperty(value = "keyCase", required = true) final Casing keyCase ) { diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TimedRefreshKeyGenerator.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TimedRefreshKeyGenerator.java index a5f2279..af753bd 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TimedRefreshKeyGenerator.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TimedRefreshKeyGenerator.java @@ -25,19 +25,18 @@ public class TimedRefreshKeyGenerator extends KeyGenerator { @JsonIgnore private String key; - @JsonProperty("refreshEvery") @Getter - private final Integer refreshEvery; + private final int refreshEvery; @JsonCreator TimedRefreshKeyGenerator( - @JsonProperty(value = "refreshEvery", required = true) final Integer refreshEvery, + @JsonProperty(value = "refreshEvery", required = true) final int refreshEvery, @JsonProperty(value = "algorithm", required = true) final KeyAlgorithm algorithm, @JsonProperty(value = "keyCase", required = true) final Casing keyCase ) { super(algorithm, keyCase); - if (refreshEvery == null || refreshEvery < 1) { - throw new TorrentClientConfigIntegrityException("refreshEvery must be greater than 0."); + if (refreshEvery < 1) { + throw new TorrentClientConfigIntegrityException("refreshEvery must be greater than 0"); } this.refreshEvery = refreshEvery; } diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TorrentVolatileRefreshKeyGenerator.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TorrentVolatileRefreshKeyGenerator.java index f6b5471..2851afc 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TorrentVolatileRefreshKeyGenerator.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/TorrentVolatileRefreshKeyGenerator.java @@ -7,7 +7,6 @@ import org.araymond.joal.core.client.emulated.generator.key.algorithm.KeyAlgorit import org.araymond.joal.core.client.emulated.utils.Casing; import org.araymond.joal.core.torrent.torrent.InfoHash; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm.java index 004def8..37f0225 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm.java @@ -9,28 +9,22 @@ import java.util.concurrent.ThreadLocalRandom; @EqualsAndHashCode public class DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm implements KeyAlgorithm { - private final Long inclusiveLowerBound; - private final Long inclusiveUpperBound; + private final long inclusiveLowerBound; + private final long inclusiveUpperBound; public DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm( - @JsonProperty(value = "inclusiveLowerBound", required = true) final Long inclusiveLowerBound, - @JsonProperty(value = "inclusiveUpperBound", required = true) final Long inclusiveUpperBound + @JsonProperty(value = "inclusiveLowerBound", required = true) final long inclusiveLowerBound, + @JsonProperty(value = "inclusiveUpperBound", required = true) final long inclusiveUpperBound ) { - if (inclusiveLowerBound == null) { - throw new TorrentClientConfigIntegrityException("inclusiveLowerBound algorithm length must not be null."); - } - if (inclusiveUpperBound == null) { - throw new TorrentClientConfigIntegrityException("inclusiveUpperBound algorithm length must not be null."); - } if (inclusiveUpperBound < inclusiveLowerBound) { - throw new TorrentClientConfigIntegrityException("inclusiveUpperBound must be greater than inclusiveLowerBound."); + throw new TorrentClientConfigIntegrityException("inclusiveUpperBound must be greater than inclusiveLowerBound"); } this.inclusiveLowerBound = inclusiveLowerBound; this.inclusiveUpperBound = inclusiveUpperBound; } - long getRandomDigitBetween(final Long minInclusive, final Long maxInclusive) { + long getRandomDigitBetween(final Long minInclusive, final long maxInclusive) { return ThreadLocalRandom.current().nextLong(minInclusive, maxInclusive + 1); } diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashKeyAlgorithm.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashKeyAlgorithm.java index a936f73..ff3da61 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashKeyAlgorithm.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashKeyAlgorithm.java @@ -4,22 +4,17 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.EqualsAndHashCode; import lombok.Getter; import org.apache.commons.lang3.RandomStringUtils; -import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException; @EqualsAndHashCode @Getter public class HashKeyAlgorithm implements KeyAlgorithm { @JsonProperty("length") - private final Integer length; + private final int length; public HashKeyAlgorithm( - @JsonProperty(value = "length", required = true) final Integer length + @JsonProperty(value = "length", required = true) final int length ) { - if (length == null) { - throw new TorrentClientConfigIntegrityException("key algorithm length must not be null."); - } - this.length = length; } diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashNoLeadingZeroKeyAlgorithm.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashNoLeadingZeroKeyAlgorithm.java index 9318bd4..1e83cab 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashNoLeadingZeroKeyAlgorithm.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashNoLeadingZeroKeyAlgorithm.java @@ -5,28 +5,24 @@ import com.google.common.annotations.VisibleForTesting; import lombok.EqualsAndHashCode; import lombok.Getter; import org.apache.commons.lang3.RandomStringUtils; -import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException; +import org.apache.commons.lang3.StringUtils; @EqualsAndHashCode @Getter public class HashNoLeadingZeroKeyAlgorithm implements KeyAlgorithm { @JsonProperty("length") - private final Integer length; + private final int length; public HashNoLeadingZeroKeyAlgorithm( - @JsonProperty(value = "length", required = true) final Integer length + @JsonProperty(value = "length", required = true) final int length ) { - if (length == null) { - throw new TorrentClientConfigIntegrityException("key algorithm length must not be null."); - } - this.length = length; } @VisibleForTesting String removeLeadingZeroes(final String string) { - return string.replaceFirst("^0+(?!$)", ""); + return StringUtils.stripStart(string, "0"); } @Override diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/numwant/NumwantProvider.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/numwant/NumwantProvider.java index 40d4710..6e94175 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/numwant/NumwantProvider.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/numwant/NumwantProvider.java @@ -10,22 +10,17 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode public class NumwantProvider { - private final Integer numwant; - private final Integer numwantOnStop; + private final int numwant; + private final int numwantOnStop; - public NumwantProvider(final Integer numwant, final Integer numwantOnStop) { - Preconditions.checkNotNull(numwant, "numwant must not be null."); - Preconditions.checkArgument(numwant > 0, "numwant must be at least 1."); - Preconditions.checkNotNull(numwantOnStop, "numwantOnStop must not be null."); - Preconditions.checkArgument(numwantOnStop >= 0, "numwantOnStop must be at least 0."); + public NumwantProvider(final int numwant, final int numwantOnStop) { + Preconditions.checkArgument(numwant > 0, "numwant must be at least 1"); + Preconditions.checkArgument(numwantOnStop >= 0, "numwantOnStop must be at least 0"); this.numwant = numwant; this.numwantOnStop = numwantOnStop; } - public Integer get(final RequestEvent event) { - if (event == RequestEvent.STOPPED) { - return numwantOnStop; - } - return numwant; + public int get(final RequestEvent event) { + return event == RequestEvent.STOPPED ? numwantOnStop : numwant; } } diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/NeverRefreshPeerIdGenerator.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/NeverRefreshPeerIdGenerator.java index e03bd37..219694a 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/NeverRefreshPeerIdGenerator.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/NeverRefreshPeerIdGenerator.java @@ -12,6 +12,7 @@ import org.araymond.joal.core.torrent.torrent.InfoHash; public class NeverRefreshPeerIdGenerator extends PeerIdGenerator { private final String peerId; + @JsonCreator NeverRefreshPeerIdGenerator( diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/PeerIdGenerator.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/PeerIdGenerator.java index f1bd5f4..e154e25 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/PeerIdGenerator.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/PeerIdGenerator.java @@ -34,7 +34,7 @@ public abstract class PeerIdGenerator { protected PeerIdGenerator(final PeerIdAlgorithm algorithm, final boolean shouldUrlEncode) { if (algorithm == null) { - throw new TorrentClientConfigIntegrityException("peerId algorithm must not be null."); + throw new TorrentClientConfigIntegrityException("peerId algorithm must not be null"); } this.algorithm = algorithm; this.shouldUrlEncode = shouldUrlEncode; @@ -46,7 +46,8 @@ public abstract class PeerIdGenerator { protected String generatePeerId() { final String peerId = this.algorithm.generate(); if (peerId.length() != PEER_ID_LENGTH) { - throw new IllegalStateException("PeerId length was supposed to be 20. But a PeerId of " + peerId.length() + " was generated. Throw exception to prevent sending invalid PeerId to tracker."); + throw new IllegalStateException("PeerId length was supposed to be " + PEER_ID_LENGTH + ", but a length of " + + peerId.length() + " was generated. Throw exception to prevent sending invalid PeerId to tracker"); } return peerId; } diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/TimedRefreshPeerIdGenerator.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/TimedRefreshPeerIdGenerator.java index 14ea76e..0721138 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/TimedRefreshPeerIdGenerator.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/TimedRefreshPeerIdGenerator.java @@ -20,20 +20,17 @@ public class TimedRefreshPeerIdGenerator extends PeerIdGenerator { @VisibleForTesting LocalDateTime lastGeneration; private String peerId; - - @JsonProperty("refreshEvery") - @Getter - private final Integer refreshEvery; + @Getter private final int refreshEvery; @JsonCreator TimedRefreshPeerIdGenerator( - @JsonProperty(value = "refreshEvery", required = true) final Integer refreshEvery, + @JsonProperty(value = "refreshEvery", required = true) final int refreshEvery, @JsonProperty(value = "algorithm", required = true) final PeerIdAlgorithm algorithm, @JsonProperty(value = "shouldUrlEncode", required = true) final boolean isUrlEncoded ) { super(algorithm, isUrlEncoded); - if (refreshEvery == null || refreshEvery < 1) { - throw new TorrentClientConfigIntegrityException("refreshEvery must be greater than 0."); + if (refreshEvery < 1) { + throw new TorrentClientConfigIntegrityException("refreshEvery must be greater than 0"); } this.refreshEvery = refreshEvery; } diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/TorrentVolatileRefreshPeerIdGenerator.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/TorrentVolatileRefreshPeerIdGenerator.java index 74cac1e..806645b 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/TorrentVolatileRefreshPeerIdGenerator.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/TorrentVolatileRefreshPeerIdGenerator.java @@ -6,7 +6,6 @@ import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.R import org.araymond.joal.core.client.emulated.generator.peerid.generation.PeerIdAlgorithm; import org.araymond.joal.core.torrent.torrent.InfoHash; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/generation/RandomPoolWithChecksumPeerIdAlgorithm.java b/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/generation/RandomPoolWithChecksumPeerIdAlgorithm.java index cf7a178..199b0f5 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/generation/RandomPoolWithChecksumPeerIdAlgorithm.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/generator/peerid/generation/RandomPoolWithChecksumPeerIdAlgorithm.java @@ -16,8 +16,8 @@ import java.time.Instant; public class RandomPoolWithChecksumPeerIdAlgorithm implements PeerIdAlgorithm { private final SecureRandom random; - private Integer refreshSeedAfter; - private Integer generationCount; + private int refreshSeedAfter; + private int generationCount; @JsonProperty("prefix") @Getter @@ -58,7 +58,7 @@ public class RandomPoolWithChecksumPeerIdAlgorithm implements PeerIdAlgorithm { return Instant.now().toString().getBytes(Charsets.UTF_8); } - private Integer getRandomIntBetween10And50() { + private int getRandomIntBetween10And50() { // Using the current random to generate another random would be completely useless because if the SecureRandom appears to be predictable we will be able to predict the next int as well int randNumber = new SecureRandom(createSecureRandomSeed()).nextInt(); randNumber = Math.abs(randNumber % 40); diff --git a/src/main/java/org/araymond/joal/core/client/emulated/utils/Casing.java b/src/main/java/org/araymond/joal/core/client/emulated/utils/Casing.java index 3314e5d..7864b8e 100644 --- a/src/main/java/org/araymond/joal/core/client/emulated/utils/Casing.java +++ b/src/main/java/org/araymond/joal/core/client/emulated/utils/Casing.java @@ -4,27 +4,28 @@ import com.fasterxml.jackson.annotation.JsonProperty; public enum Casing { @JsonProperty("upper") - UPPER, - @JsonProperty("lower") - LOWER, - @JsonProperty("none") - NONE; - - public String toCase(final String str) { - final String value; - switch (this) { - case UPPER: - value = str.toUpperCase(); - break; - case LOWER: - value = str.toLowerCase(); - break; - case NONE: - value = str; - break; - default: - throw new IllegalStateException("Unhandled type: " + this.name()); + UPPER { + @Override + public String toCase(String str) { + return str.toUpperCase(); } - return value; - } + }, + + @JsonProperty("lower") + LOWER { + @Override + public String toCase(String str) { + return str.toLowerCase(); + } + }, + + @JsonProperty("none") + NONE { + @Override + public String toCase(String str) { + return str; + } + }; + + public abstract String toCase(String str); } diff --git a/src/main/java/org/araymond/joal/core/config/AppConfiguration.java b/src/main/java/org/araymond/joal/core/config/AppConfiguration.java index f2fe851..bdab53b 100644 --- a/src/main/java/org/araymond/joal/core/config/AppConfiguration.java +++ b/src/main/java/org/araymond/joal/core/config/AppConfiguration.java @@ -15,19 +15,17 @@ import org.apache.commons.lang3.StringUtils; @Getter public class AppConfiguration { - private final Long minUploadRate; - private final Long maxUploadRate; - private final Integer simultaneousSeed; - @JsonProperty("client") + private final long minUploadRate; + private final long maxUploadRate; + private final int simultaneousSeed; private final String client; - @JsonProperty("keepTorrentWithZeroLeechers") private final boolean keepTorrentWithZeroLeechers; @JsonCreator public AppConfiguration( - @JsonProperty(value = "minUploadRate", required = true) final Long minUploadRate, - @JsonProperty(value = "maxUploadRate", required = true) final Long maxUploadRate, - @JsonProperty(value = "simultaneousSeed", required = true) final Integer simultaneousSeed, + @JsonProperty(value = "minUploadRate", required = true) final long minUploadRate, + @JsonProperty(value = "maxUploadRate", required = true) final long maxUploadRate, + @JsonProperty(value = "simultaneousSeed", required = true) final int simultaneousSeed, @JsonProperty(value = "client", required = true) final String client, @JsonProperty(value = "keepTorrentWithZeroLeechers", required = true) final boolean keepTorrentWithZeroLeechers ) { @@ -41,28 +39,22 @@ public class AppConfiguration { } private void validate() { - if (minUploadRate == null) { - throw new AppConfigurationIntegrityException("minUploadRate must not be null"); - } else if (minUploadRate < 0L) { - throw new AppConfigurationIntegrityException("minUploadRate must be at least 0."); + if (minUploadRate < 0) { + throw new AppConfigurationIntegrityException("minUploadRate must be at least 0"); } - if (maxUploadRate == null) { - throw new AppConfigurationIntegrityException("maxUploadRate must not be null"); - } else if (maxUploadRate < 0L) { - throw new AppConfigurationIntegrityException("maxUploadRate must greater or equal to 0."); + if (maxUploadRate < 0) { + throw new AppConfigurationIntegrityException("maxUploadRate must greater or equal to 0"); } else if (maxUploadRate < minUploadRate) { - throw new AppConfigurationIntegrityException("maxUploadRate must be greater or equal to minUploadRate."); + throw new AppConfigurationIntegrityException("maxUploadRate must be greater or equal to minUploadRate"); } - if (simultaneousSeed == null) { - throw new AppConfigurationIntegrityException("simultaneousSeed must not be null"); - } else if (simultaneousSeed < 1) { - throw new AppConfigurationIntegrityException("simultaneousSeed must be greater than 0."); + if (simultaneousSeed < 1) { + throw new AppConfigurationIntegrityException("simultaneousSeed must be greater than 0"); } if (StringUtils.isBlank(client)) { - throw new AppConfigurationIntegrityException("client is required, no file name given."); + throw new AppConfigurationIntegrityException("client is required, no file name given"); } } } diff --git a/src/main/java/org/araymond/joal/core/config/JoalConfigProvider.java b/src/main/java/org/araymond/joal/core/config/JoalConfigProvider.java index ad5b0d5..1cf1c88 100644 --- a/src/main/java/org/araymond/joal/core/config/JoalConfigProvider.java +++ b/src/main/java/org/araymond/joal/core/config/JoalConfigProvider.java @@ -11,35 +11,41 @@ import org.springframework.context.ApplicationEventPublisher; import javax.inject.Provider; import java.io.FileNotFoundException; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; +import static java.lang.String.format; +import static java.nio.file.Files.isRegularFile; + /** + * Handles the serialization & deserialization of our main app config file. + *

* Created by raymo on 18/04/2017. */ @Slf4j public class JoalConfigProvider implements Provider { private static final String CONF_FILE_NAME = "config.json"; - private final Path joalConfPath; + private final Path joalConfFile; private final ObjectMapper objectMapper; - private AppConfiguration config = null; - private final ApplicationEventPublisher publisher; + private AppConfiguration config; + private final ApplicationEventPublisher appEventPublisher; - public JoalConfigProvider(final ObjectMapper objectMapper, final SeedManager.JoalFoldersPath joalFoldersPath, final ApplicationEventPublisher publisher) throws FileNotFoundException { + public JoalConfigProvider(final ObjectMapper objectMapper, final SeedManager.JoalFoldersPath joalFoldersPath, + final ApplicationEventPublisher appEventPublisher) throws FileNotFoundException { this.objectMapper = objectMapper; - this.publisher = publisher; + this.appEventPublisher = appEventPublisher; - this.joalConfPath = joalFoldersPath.getConfPath().resolve(CONF_FILE_NAME); - if (!Files.isRegularFile(joalConfPath)) { - throw new FileNotFoundException(String.format("App configuration file [%s] not found", joalConfPath)); + this.joalConfFile = joalFoldersPath.getConfDirRootPath().resolve(CONF_FILE_NAME); + if (!isRegularFile(joalConfFile)) { + throw new FileNotFoundException(format("App configuration file [%s] not found", joalConfFile)); } - log.debug("App configuration file will be searched for in [{}]", joalConfPath.toAbsolutePath()); + log.debug("App configuration file will be searched for in [{}]", joalConfFile.toAbsolutePath()); } - public void init() { + public AppConfiguration init() { this.config = this.loadConfiguration(); + return this.config; } @Override @@ -53,10 +59,10 @@ public class JoalConfigProvider implements Provider { @VisibleForTesting AppConfiguration loadConfiguration() { - final AppConfiguration configuration; + final AppConfiguration conf; try { - log.debug("Reading json configuration from [{}]", joalConfPath.toAbsolutePath()); - configuration = objectMapper.readValue(joalConfPath.toFile(), AppConfiguration.class); + log.debug("Reading json configuration from [{}]...", joalConfFile.toAbsolutePath()); + conf = objectMapper.readValue(joalConfFile.toFile(), AppConfiguration.class); log.debug("Successfully read json configuration"); } catch (final IOException e) { log.error("Failed to read configuration file", e); @@ -64,14 +70,15 @@ public class JoalConfigProvider implements Provider { } log.info("App configuration has been successfully loaded"); - this.publisher.publishEvent(new ConfigHasBeenLoadedEvent(configuration)); - return configuration; + this.appEventPublisher.publishEvent(new ConfigHasBeenLoadedEvent(conf)); + return conf; } + // TODO: verify that the new config ends up under this.config after saving new! public void saveNewConf(final AppConfiguration conf) { try { - objectMapper.writerWithDefaultPrettyPrinter().writeValue(joalConfPath.toFile(), conf); - publisher.publishEvent(new ConfigurationIsInDirtyStateEvent(conf)); + objectMapper.writerWithDefaultPrettyPrinter().writeValue(joalConfFile.toFile(), conf); + appEventPublisher.publishEvent(new ConfigurationIsInDirtyStateEvent(conf)); } catch (final IOException e) { log.error("Failed to write new configuration file", e); throw new IllegalStateException(e); diff --git a/src/main/java/org/araymond/joal/core/events/config/ConfigHasBeenLoadedEvent.java b/src/main/java/org/araymond/joal/core/events/config/ConfigHasBeenLoadedEvent.java index 3156c42..739e851 100644 --- a/src/main/java/org/araymond/joal/core/events/config/ConfigHasBeenLoadedEvent.java +++ b/src/main/java/org/araymond/joal/core/events/config/ConfigHasBeenLoadedEvent.java @@ -12,7 +12,7 @@ public class ConfigHasBeenLoadedEvent { private final AppConfiguration configuration; public ConfigHasBeenLoadedEvent(final AppConfiguration configuration) { - Preconditions.checkNotNull(configuration, "Configuration must not be null."); + Preconditions.checkNotNull(configuration, "Configuration must not be null"); this.configuration = configuration; } } diff --git a/src/main/java/org/araymond/joal/core/events/config/ConfigurationIsInDirtyStateEvent.java b/src/main/java/org/araymond/joal/core/events/config/ConfigurationIsInDirtyStateEvent.java index f2d7633..4fe23f9 100644 --- a/src/main/java/org/araymond/joal/core/events/config/ConfigurationIsInDirtyStateEvent.java +++ b/src/main/java/org/araymond/joal/core/events/config/ConfigurationIsInDirtyStateEvent.java @@ -2,7 +2,6 @@ package org.araymond.joal.core.events.config; import com.google.common.base.Preconditions; import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.araymond.joal.core.config.AppConfiguration; /** diff --git a/src/main/java/org/araymond/joal/core/torrent/torrent/InfoHash.java b/src/main/java/org/araymond/joal/core/torrent/torrent/InfoHash.java index 84f4f41..5703338 100644 --- a/src/main/java/org/araymond/joal/core/torrent/torrent/InfoHash.java +++ b/src/main/java/org/araymond/joal/core/torrent/torrent/InfoHash.java @@ -4,6 +4,10 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; +import java.util.regex.Pattern; + +import static org.apache.commons.lang3.StringUtils.EMPTY; + @ToString @EqualsAndHashCode(of = "infoHash") @Getter @@ -11,9 +15,11 @@ public class InfoHash { private final String infoHash; private final String humanReadable; + private static final Pattern INVISIBLE_CTRL_CHARS_PTRN = Pattern.compile("\\p{C}"); + public InfoHash(final byte[] bytes) { this.infoHash = new String(bytes, MockedTorrent.BYTE_ENCODING); - this.humanReadable = infoHash.replaceAll("\\p{C}", ""); + this.humanReadable = INVISIBLE_CTRL_CHARS_PTRN.matcher(infoHash).replaceAll(EMPTY); } public String value() { diff --git a/src/main/java/org/araymond/joal/core/torrent/torrent/MockedTorrent.java b/src/main/java/org/araymond/joal/core/torrent/torrent/MockedTorrent.java index a18b9c1..ab6a367 100644 --- a/src/main/java/org/araymond/joal/core/torrent/torrent/MockedTorrent.java +++ b/src/main/java/org/araymond/joal/core/torrent/torrent/MockedTorrent.java @@ -1,9 +1,9 @@ package org.araymond.joal.core.torrent.torrent; -import com.google.common.base.Charsets; import com.turn.ttorrent.bcodec.InvalidBEncodingException; import com.turn.ttorrent.common.Torrent; import lombok.EqualsAndHashCode; +import lombok.Getter; import org.apache.commons.io.FileUtils; import java.io.File; @@ -16,11 +16,13 @@ import java.security.NoSuchAlgorithmException; * Created by raymo on 23/01/2017. */ @SuppressWarnings("ClassWithOnlyPrivateConstructors") -@EqualsAndHashCode +@EqualsAndHashCode(callSuper = false) +@Getter public class MockedTorrent extends Torrent { - public static final Charset BYTE_ENCODING = Charsets.ISO_8859_1; + public static final Charset BYTE_ENCODING = Charset.forName(Torrent.BYTE_ENCODING); + + private final InfoHash torrentInfoHash; - private final InfoHash infoHash; /** * Create a new torrent from meta-info binary data. *

@@ -28,12 +30,11 @@ public class MockedTorrent extends Torrent { * BitTorrent specification) and create a Torrent object from it. * * @param torrent The meta-info byte data. - * @param seeder Whether we'll be seeding for this torrent or not. * @throws IOException When the info dictionary can't be read or * encoded and hashed back to create the torrent's SHA-1 hash. */ - private MockedTorrent(final byte[] torrent, final boolean seeder) throws IOException, NoSuchAlgorithmException { - super(torrent, seeder); + private MockedTorrent(final byte[] torrent) throws IOException, NoSuchAlgorithmException { + super(torrent, false); try { // Torrent validity tests @@ -46,19 +47,16 @@ public class MockedTorrent extends Torrent { } catch (final InvalidBEncodingException ex) { throw new IllegalArgumentException("Error reading torrent meta-info fields!", ex); } - this.infoHash = new InfoHash(this.getInfoHash()); + + this.torrentInfoHash = new InfoHash(this.getInfoHash()); } - public InfoHash getTorrentInfoHash() { - return this.infoHash; - } - - public static MockedTorrent fromFile(final File torrent) throws IOException, NoSuchAlgorithmException { - final byte[] data = FileUtils.readFileToByteArray(torrent); - return new MockedTorrent(data, true); + public static MockedTorrent fromFile(final File torrentFile) throws IOException, NoSuchAlgorithmException { + final byte[] data = FileUtils.readFileToByteArray(torrentFile); + return new MockedTorrent(data); } public static MockedTorrent fromBytes(final byte[] bytes) throws IOException, NoSuchAlgorithmException { - return new MockedTorrent(bytes, false); + return new MockedTorrent(bytes); } } diff --git a/src/main/java/org/araymond/joal/core/torrent/watcher/TorrentFileProvider.java b/src/main/java/org/araymond/joal/core/torrent/watcher/TorrentFileProvider.java index a31e635..58c84a7 100644 --- a/src/main/java/org/araymond/joal/core/torrent/watcher/TorrentFileProvider.java +++ b/src/main/java/org/araymond/joal/core/torrent/watcher/TorrentFileProvider.java @@ -19,37 +19,55 @@ import java.util.*; import java.util.stream.Collectors; import static java.lang.String.format; +import static java.nio.file.Files.isDirectory; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.util.Collections.synchronizedMap; import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; /** + * With help of a {@link TorrentFileWatcher}, it monitors our filesystem for + * {@code .torrent} file additions, changes & deletions, and processes these + * events accordingly. + *

+ * Note this class itself implements {@link FileAlterationListenerAdaptor} that's + * registered by our {@link TorrentFileWatcher}. But we expose methods to register + * JOAL-specific listeners implementing {@link TorrentFileChangeAware} that will + * be notified of file system changes on specific torrent files. + *

* Created by raymo on 28/01/2017. */ @Slf4j public class TorrentFileProvider extends FileAlterationListenerAdaptor { private final TorrentFileWatcher watcher; - private final Map torrentFiles = Collections.synchronizedMap(new HashMap<>()); - private final Set torrentFileChangeListener; + private final Map torrentFiles = synchronizedMap(new HashMap<>()); + private final Set torrentFileChangeListeners; private final Path archiveFolder; public TorrentFileProvider(final SeedManager.JoalFoldersPath joalFoldersPath) throws FileNotFoundException { - Path torrentFolder = joalFoldersPath.getTorrentFilesPath(); - if (!Files.isDirectory(torrentFolder)) { + Path torrentsDir = joalFoldersPath.getTorrentsDirPath(); + if (!isDirectory(torrentsDir)) { // TODO: shouldn't we check&throw in JoalFoldersPath instead? - log.error("Folder [{}] does not exist", torrentFolder.toAbsolutePath()); - throw new FileNotFoundException(format("Torrent folder [%s] not found", torrentFolder.toAbsolutePath())); + log.error("Folder [{}] does not exist", torrentsDir.toAbsolutePath()); + throw new FileNotFoundException(format("Torrent folder [%s] not found", torrentsDir.toAbsolutePath())); } - this.archiveFolder = joalFoldersPath.getTorrentArchivedPath(); - this.watcher = new TorrentFileWatcher(this, torrentFolder); - this.torrentFileChangeListener = new HashSet<>(); + this.archiveFolder = joalFoldersPath.getTorrentArchiveDirPath(); + this.watcher = new TorrentFileWatcher(this, torrentsDir); + this.torrentFileChangeListeners = new HashSet<>(); + } + + public void start() { + this.init(); + this.watcher.start(); } @VisibleForTesting void init() { - if (!Files.isDirectory(archiveFolder)) { + if (!isDirectory(archiveFolder)) { if (Files.exists(archiveFolder)) { - String errMsg = "archive folder exists, but is not a directory"; + String errMsg = "Archive folder exists, but is not a directory"; log.error(errMsg); throw new IllegalStateException(errMsg); } @@ -64,11 +82,6 @@ public class TorrentFileProvider extends FileAlterationListenerAdaptor { } } - public void start() { - this.init(); - this.watcher.start(); - } - public void stop() { this.watcher.stop(); this.torrentFiles.clear(); @@ -79,7 +92,7 @@ public class TorrentFileProvider extends FileAlterationListenerAdaptor { ofNullable(this.torrentFiles.remove(file)) .ifPresent(removedTorrent -> { log.info("Torrent file deleting detected, hot deleted file [{}]", file.getAbsolutePath()); - this.torrentFileChangeListener.forEach(listener -> listener.onTorrentFileRemoved(removedTorrent)); + this.torrentFileChangeListeners.forEach(listener -> listener.onTorrentFileRemoved(removedTorrent)); }); } @@ -89,13 +102,13 @@ public class TorrentFileProvider extends FileAlterationListenerAdaptor { try { final MockedTorrent torrent = MockedTorrent.fromFile(file); this.torrentFiles.put(file, torrent); - this.torrentFileChangeListener.forEach(listener -> listener.onTorrentFileAdded(torrent)); + this.torrentFileChangeListeners.forEach(listener -> listener.onTorrentFileAdded(torrent)); } catch (final IOException | NoSuchAlgorithmException e) { - log.warn("Failed to read file [{}], moved to archive folder", file.getAbsolutePath(), e); + log.warn("Failed to read file [{}], moved to archive folder: {}", file.getAbsolutePath(), e); this.moveToArchiveFolder(file); } catch (final Exception e) { // This thread MUST NOT crash. we need handle any other exception - log.warn("Unexpected exception was caught for file [{}], moved to archive folder", file.getAbsolutePath(), e); + log.error("Unexpected exception was caught for file [{}], moved to archive folder: {}", file.getAbsolutePath(), e); this.moveToArchiveFolder(file); } } @@ -108,47 +121,46 @@ public class TorrentFileProvider extends FileAlterationListenerAdaptor { } public void registerListener(final TorrentFileChangeAware listener) { - this.torrentFileChangeListener.add(listener); + this.torrentFileChangeListeners.add(listener); } public void unRegisterListener(final TorrentFileChangeAware listener) { - this.torrentFileChangeListener.remove(listener); + this.torrentFileChangeListeners.remove(listener); } - public MockedTorrent getTorrentNotIn(final List unwantedTorrents) throws NoMoreTorrentsFileAvailableException { - Preconditions.checkNotNull(unwantedTorrents, "List of unwantedTorrents cannot be null."); + public MockedTorrent getTorrentNotIn(final Collection unwantedTorrents) throws NoMoreTorrentsFileAvailableException { + Preconditions.checkNotNull(unwantedTorrents, "unwantedTorrents cannot be null"); return this.torrentFiles.values().stream() .filter(torrent -> !unwantedTorrents.contains(torrent.getTorrentInfoHash())) - .collect(Collectors.collectingAndThen(Collectors.toList(), collected -> { + .collect(Collectors.collectingAndThen(toList(), collected -> { Collections.shuffle(collected); return collected.stream(); })) .findAny() - .orElseThrow(() -> new NoMoreTorrentsFileAvailableException("No more torrent file available.")); + .orElseThrow(() -> new NoMoreTorrentsFileAvailableException("No more torrent files available")); } void moveToArchiveFolder(final File torrentFile) { - if (!torrentFile.exists()) { + if (torrentFile == null || !torrentFile.exists()) { return; } this.onFileDelete(torrentFile); try { Path moveTarget = archiveFolder.resolve(torrentFile.getName()); - Files.deleteIfExists(moveTarget); - Files.move(torrentFile.toPath(), moveTarget); + Files.move(torrentFile.toPath(), moveTarget, REPLACE_EXISTING); log.info("Successfully moved file [{}] to archive folder", torrentFile.getAbsolutePath()); } catch (final IOException e) { - log.warn("Failed to archive file [{}], the file won't be used anymore for the current session, but it remains in the folder", torrentFile.getAbsolutePath()); + log.error("Failed to archive file [{}], the file won't be used anymore for the current session, but it remains in the folder", torrentFile.getAbsolutePath()); } } public void moveToArchiveFolder(final InfoHash infoHash) { this.torrentFiles.entrySet().stream() .filter(entry -> entry.getValue().getTorrentInfoHash().equals(infoHash)) - .map(Map.Entry::getKey) .findAny() + .map(Map.Entry::getKey) .ifPresentOrElse(this::moveToArchiveFolder, () -> log.warn("Cannot move torrent [{}] to archive folder. Torrent file seems not to be registered in TorrentFileProvider", infoHash)); } diff --git a/src/main/java/org/araymond/joal/core/torrent/watcher/TorrentFileWatcher.java b/src/main/java/org/araymond/joal/core/torrent/watcher/TorrentFileWatcher.java index fab1617..a9c8407 100644 --- a/src/main/java/org/araymond/joal/core/torrent/watcher/TorrentFileWatcher.java +++ b/src/main/java/org/araymond/joal/core/torrent/watcher/TorrentFileWatcher.java @@ -4,27 +4,28 @@ import com.google.common.base.Preconditions; import com.google.common.util.concurrent.ThreadFactoryBuilder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.io.monitor.FileAlterationListener; import org.apache.commons.io.monitor.FileAlterationMonitor; import org.apache.commons.io.monitor.FileAlterationObserver; import java.io.File; -import java.nio.file.Files; import java.nio.file.Path; +import static java.nio.file.Files.isDirectory; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.commons.io.filefilter.FileFilterUtils.suffixFileFilter; + /** * Hooks up a directory listener to detect torrent file additions, - * deletions, changes. - * + * deletions, changes, and notifies provided {@link FileAlterationListener}. + *

* Created by raymo on 01/05/2017. */ @Slf4j class TorrentFileWatcher { - // TODO : get back to a bigger value as soon as https://issues.apache.org/jira/browse/IO-535 is fixed - private static final Integer DEFAULT_SCAN_INTERVAL_MS = 2 * 1000; - private static final IOFileFilter TORRENT_FILE_FILTER = FileFilterUtils.suffixFileFilter(".torrent"); + private static final long DEFAULT_SCAN_INTERVAL_MS = SECONDS.toMillis(5); + private static final IOFileFilter TORRENT_FILE_FILTER = suffixFileFilter(".torrent"); private final FileAlterationObserver observer; private final FileAlterationListener listener; @@ -35,11 +36,10 @@ class TorrentFileWatcher { this(listener, monitoredFolder, DEFAULT_SCAN_INTERVAL_MS); } - TorrentFileWatcher(final FileAlterationListener listener, final Path monitoredFolder, final Integer intervalMs) { + TorrentFileWatcher(final FileAlterationListener listener, final Path monitoredFolder, final long intervalMs) { Preconditions.checkNotNull(listener, "listener cannot be null"); Preconditions.checkNotNull(monitoredFolder, "monitoredFolder cannot be null"); - Preconditions.checkArgument(Files.exists(monitoredFolder), "Folder [" + monitoredFolder.toAbsolutePath() + "] does not exists."); - Preconditions.checkNotNull(intervalMs, "intervalMs cannot be null"); + Preconditions.checkArgument(isDirectory(monitoredFolder), "Folder [" + monitoredFolder.toAbsolutePath() + "] does not exists."); Preconditions.checkArgument(intervalMs > 0, "intervalMs cannot be less than 1"); this.listener = listener; @@ -55,8 +55,8 @@ class TorrentFileWatcher { void start() { try { this.monitor.start(); - // Trigger event for already present file - FileUtils.listFiles(this.monitoredFolder, TorrentFileWatcher.TORRENT_FILE_FILTER, null) + // Trigger event for already present files: + FileUtils.listFiles(this.monitoredFolder, TORRENT_FILE_FILTER, null) .forEach(this.listener::onFileCreate); } catch (final Exception e) { log.error("Failed to start torrent file monitoring", e); @@ -65,7 +65,7 @@ class TorrentFileWatcher { } void stop() { - log.trace("Stopping TorrentFileProvider"); + log.trace("Stopping TorrentFileProvider..."); this.observer.getListeners().forEach(observer::removeListener); try { this.monitor.stop(10); diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java b/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java index 4fa6a8a..2c087bb 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java @@ -21,35 +21,45 @@ import org.springframework.context.ApplicationEventPublisher; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import static java.util.stream.Collectors.toList; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.stream.Collectors.toSet; +/** + * This class is a torrent client agnostic representation. It + *

    + *
  • keeps track of & manages all the queued {@link AnnounceRequest}s via {@link DelayQueue}
  • + *
  • spawns a thread periodically going through the {@code delayQueue}, and generating + * tracker announcements off it
  • + *
  • implements {@link TorrentFileChangeAware} to react to torrent file changes in filesystem
  • + *
+ */ public class Client implements TorrentFileChangeAware, ClientFacade { - private final AppConfiguration appConfiguration; + private final AppConfiguration appConfig; private final TorrentFileProvider torrentFileProvider; private final ApplicationEventPublisher eventPublisher; private AnnouncerExecutor announcerExecutor; - private final List currentlySeedingAnnouncer; private final DelayQueue delayQueue; private final AnnouncerFactory announcerFactory; - private final ReentrantReadWriteLock lock; + private final List currentlySeedingAnnouncers = new ArrayList<>(); + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private Thread thread; private volatile boolean stop = true; - Client(final AppConfiguration appConfiguration, final TorrentFileProvider torrentFileProvider, final AnnouncerExecutor announcerExecutor, final DelayQueue delayQueue, final AnnouncerFactory announcerFactory, final ApplicationEventPublisher eventPublisher) { - Preconditions.checkNotNull(appConfiguration, "AppConfiguration must not be null"); + Client(final AppConfiguration appConfig, final TorrentFileProvider torrentFileProvider, final AnnouncerExecutor announcerExecutor, + final DelayQueue delayQueue, final AnnouncerFactory announcerFactory, final ApplicationEventPublisher eventPublisher) { + Preconditions.checkNotNull(appConfig, "AppConfiguration must not be null"); Preconditions.checkNotNull(torrentFileProvider, "TorrentFileProvider must not be null"); Preconditions.checkNotNull(delayQueue, "DelayQueue must not be null"); Preconditions.checkNotNull(announcerFactory, "AnnouncerFactory must not be null"); this.eventPublisher = eventPublisher; - this.appConfiguration = appConfiguration; + this.appConfig = appConfig; this.torrentFileProvider = torrentFileProvider; this.announcerExecutor = announcerExecutor; this.delayQueue = delayQueue; this.announcerFactory = announcerFactory; - this.currentlySeedingAnnouncer = new ArrayList<>(); - this.lock = new ReentrantReadWriteLock(); } @VisibleForTesting @@ -61,58 +71,70 @@ public class Client implements TorrentFileChangeAware, ClientFacade { public void start() { this.stop = false; + // TODO: use @Scheduled or something similar instead of looping manually over X period this.thread = new Thread(() -> { while (!this.stop) { - for (final AnnounceRequest req : this.delayQueue.getAvailables()) { + this.delayQueue.getAvailables().forEach(req -> { this.announcerExecutor.execute(req); try { this.lock.writeLock().lock(); - this.currentlySeedingAnnouncer.removeIf(an -> an.equals(req.getAnnouncer())); // remove the last recorded event - this.currentlySeedingAnnouncer.add(req.getAnnouncer()); + this.currentlySeedingAnnouncers.remove(req.getAnnouncer()); + this.currentlySeedingAnnouncers.add(req.getAnnouncer()); } finally { this.lock.writeLock().unlock(); } - } + }); try { - Thread.sleep(1000); + MILLISECONDS.sleep(1000); // TODO: move to config } catch (final InterruptedException ignored) { } } }); - for (int i = 0; i < this.appConfiguration.getSimultaneousSeed(); i++) { + // start off by populating our state with the max concurrent torrents: + Lock lock = this.lock.writeLock(); + for (int i = 0; i < this.appConfig.getSimultaneousSeed(); i++) { try { - this.lock.writeLock().lock(); - this.addTorrent(); + lock.lock(); + this.addTorrentFromDirectory(); } catch (final NoMoreTorrentsFileAvailableException ignored) { break; } finally { - this.lock.writeLock().unlock(); + lock.unlock(); } } this.thread.setName("client-orchestrator-thread"); - this.thread.start(); + this.torrentFileProvider.registerListener(this); } - private void addTorrent() throws NoMoreTorrentsFileAvailableException { + /** + * Polls a new torrent file from the directory that's not currently being tracked. + */ + private void addTorrentFromDirectory() throws NoMoreTorrentsFileAvailableException { final MockedTorrent torrent = this.torrentFileProvider.getTorrentNotIn( - this.currentlySeedingAnnouncer.stream() + this.currentlySeedingAnnouncers.stream() .map(Announcer::getTorrentInfoHash) - .collect(toList()) + .collect(toSet()) ); + + addTorrent(torrent); + } + + private void addTorrent(MockedTorrent torrent) { final Announcer announcer = this.announcerFactory.create(torrent); - this.currentlySeedingAnnouncer.add(announcer); + this.currentlySeedingAnnouncers.add(announcer); this.delayQueue.addOrReplace(AnnounceRequest.createStart(announcer), 0, ChronoUnit.SECONDS); } @Override public void stop() { + Lock lock = this.lock.writeLock(); try { - this.lock.writeLock().lock(); + lock.lock(); this.stop = true; this.torrentFileProvider.unRegisterListener(this); if (this.thread != null) { @@ -120,102 +142,104 @@ public class Client implements TorrentFileChangeAware, ClientFacade { try { this.thread.join(); } catch (final InterruptedException ignored) { + } finally { + this.thread = null; } - this.thread = null; } this.delayQueue.drainAll().stream() - .filter(req -> req.getEvent() != RequestEvent.STARTED) - .map(AnnounceRequest::getAnnouncer) - .map(AnnounceRequest::createStop) + .filter(req -> req.getEvent() != RequestEvent.STARTED) // no need to generate 'stopped' request if the 'started' req was still waiting in queue + .map(AnnounceRequest::toStop) .forEach(this.announcerExecutor::execute); this.announcerExecutor.awaitForRunningTasks(); } finally { - this.lock.writeLock().unlock(); + lock.unlock(); } } - public void onTooManyFailedInARaw(final Announcer announcer) { + public void onTooManyFailedInARow(final Announcer announcer) { if (this.stop) { - this.currentlySeedingAnnouncer.remove(announcer); + this.currentlySeedingAnnouncers.remove(announcer); return; } + Lock lock = this.lock.writeLock(); try { - this.lock.writeLock().lock(); - this.currentlySeedingAnnouncer.remove(announcer); // Remove from announcers list asap, otherwise the deletion will trigger a announce stop event. + lock.lock(); + this.currentlySeedingAnnouncers.remove(announcer); // Remove from announcers list asap, otherwise the deletion will trigger an announce stop event. this.torrentFileProvider.moveToArchiveFolder(announcer.getTorrentInfoHash()); - this.addTorrent(); + this.addTorrentFromDirectory(); } catch (final NoMoreTorrentsFileAvailableException ignored) { } finally { - this.lock.writeLock().unlock(); + lock.unlock(); } } public void onNoMorePeers(final InfoHash infoHash) { - if (!this.appConfiguration.isKeepTorrentWithZeroLeechers()) { + if (!this.appConfig.isKeepTorrentWithZeroLeechers()) { this.torrentFileProvider.moveToArchiveFolder(infoHash); } } public void onTorrentHasStopped(final Announcer stoppedAnnouncer) { if (this.stop) { - this.currentlySeedingAnnouncer.remove(stoppedAnnouncer); + this.currentlySeedingAnnouncers.remove(stoppedAnnouncer); return; } - try { - this.lock.writeLock().lock(); - this.addTorrent(); + Lock lock = this.lock.writeLock(); + try { + lock.lock(); + this.addTorrentFromDirectory(); } catch (final NoMoreTorrentsFileAvailableException ignored) { } finally { - this.currentlySeedingAnnouncer.remove(stoppedAnnouncer); - this.lock.writeLock().unlock(); + this.currentlySeedingAnnouncers.remove(stoppedAnnouncer); + lock.unlock(); } } @Override public void onTorrentFileAdded(final MockedTorrent torrent) { this.eventPublisher.publishEvent(new TorrentFileAddedEvent(torrent)); - if (this.stop) { - return; - } - try { - this.lock.writeLock().lock(); - if (this.currentlySeedingAnnouncer.size() >= this.appConfiguration.getSimultaneousSeed()) { - return; + + if (!this.stop && this.currentlySeedingAnnouncers.size() < this.appConfig.getSimultaneousSeed()) { + Lock lock = this.lock.writeLock(); + try { + lock.lock(); + addTorrent(torrent); + } finally { + lock.unlock(); } - final Announcer announcer = this.announcerFactory.create(torrent); - this.currentlySeedingAnnouncer.add(announcer); - this.delayQueue.addOrReplace(AnnounceRequest.createStart(announcer), 1, ChronoUnit.SECONDS); - } finally { - this.lock.writeLock().unlock(); } } @Override public void onTorrentFileRemoved(final MockedTorrent torrent) { this.eventPublisher.publishEvent(new TorrentFileDeletedEvent(torrent)); + Lock lock = this.lock.writeLock(); try { - this.lock.writeLock().lock(); - this.currentlySeedingAnnouncer.stream() + lock.lock(); + this.currentlySeedingAnnouncers.stream() .filter(announcer -> announcer.getTorrentInfoHash().equals(torrent.getTorrentInfoHash())) - .findFirst() + .findAny() .ifPresent(announcer -> - this.delayQueue.addOrReplace(AnnounceRequest.createStop(announcer), 1, ChronoUnit.SECONDS) + this.delayQueue.addOrReplace( + AnnounceRequest.createStop(announcer), 1, ChronoUnit.SECONDS + ) ); } finally { - this.lock.writeLock().unlock(); + lock.unlock(); } } @Override - public List getCurrentlySeedingAnnouncer() { + public List getCurrentlySeedingAnnouncers() { + Lock lock = this.lock.readLock(); try { - this.lock.readLock().lock(); - return new ArrayList<>(this.currentlySeedingAnnouncer); + lock.lock(); + return new ArrayList<>(this.currentlySeedingAnnouncers); } finally { - this.lock.readLock().unlock(); + lock.unlock(); } } } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/ClientBuilder.java b/src/main/java/org/araymond/joal/core/ttorrent/client/ClientBuilder.java index 598fded..d2a4ecd 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/ClientBuilder.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/ClientBuilder.java @@ -1,5 +1,7 @@ package org.araymond.joal.core.ttorrent.client; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.araymond.joal.core.bandwith.BandwidthDispatcher; import org.araymond.joal.core.config.AppConfiguration; import org.araymond.joal.core.torrent.watcher.TorrentFileProvider; @@ -9,6 +11,7 @@ import org.araymond.joal.core.ttorrent.client.announcer.request.AnnouncerExecuto import org.araymond.joal.core.ttorrent.client.announcer.response.*; import org.springframework.context.ApplicationEventPublisher; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class ClientBuilder { private AppConfiguration appConfiguration; private TorrentFileProvider torrentFileProvider; @@ -17,10 +20,6 @@ public final class ClientBuilder { private ApplicationEventPublisher eventPublisher; private DelayQueue delayQueue; - private ClientBuilder() { - // private - } - public static ClientBuilder builder() { return new ClientBuilder(); } @@ -57,18 +56,16 @@ public final class ClientBuilder { public ClientFacade build() { final AnnounceResponseHandlerChain announceResponseCallback = new AnnounceResponseHandlerChain(); - announceResponseCallback.appendHandler(new AnnounceEventPublisher(this.eventPublisher)); - announceResponseCallback.appendHandler(new AnnounceReEnqueuer(this.delayQueue)); + announceResponseCallback.appendHandler(new AnnounceEventPublisher(eventPublisher)); + announceResponseCallback.appendHandler(new AnnounceReEnqueuer(delayQueue)); announceResponseCallback.appendHandler(new BandwidthDispatcherNotifier(bandwidthDispatcher)); - final ClientNotifier clientNotifier = new ClientNotifier(); - announceResponseCallback.appendHandler(clientNotifier); final AnnouncerExecutor announcerExecutor = new AnnouncerExecutor(announceResponseCallback); - final Client client = new Client(this.appConfiguration, this.torrentFileProvider, announcerExecutor, this.delayQueue, this.announcerFactory, this.eventPublisher); - clientNotifier.setClient(client); + final Client client = new Client(this.appConfiguration, this.torrentFileProvider, announcerExecutor, + this.delayQueue, this.announcerFactory, this.eventPublisher); + announceResponseCallback.appendHandler(new ClientNotifier(client)); return client; } - } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/ClientFacade.java b/src/main/java/org/araymond/joal/core/ttorrent/client/ClientFacade.java index bc85088..42d16cf 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/ClientFacade.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/ClientFacade.java @@ -7,5 +7,5 @@ import java.util.List; public interface ClientFacade { void start(); void stop(); - List getCurrentlySeedingAnnouncer(); + List getCurrentlySeedingAnnouncers(); } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/ConnectionHandler.java b/src/main/java/org/araymond/joal/core/ttorrent/client/ConnectionHandler.java index b431e4f..f94bed3 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/ConnectionHandler.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/ConnectionHandler.java @@ -2,7 +2,6 @@ package org.araymond.joal.core.ttorrent.client; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; -import com.google.common.collect.Lists; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -12,11 +11,22 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.net.*; import java.nio.channels.ServerSocketChannel; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; +import static java.util.concurrent.TimeUnit.MINUTES; + /** + * This class has 2 main functions: + *
    + *
  • establishes a socket on an open port that accepts connections
  • + *
  • periodically resolves our external IP address
  • + *
+ * Note this port & IP will be reported back to trackers via announcements, if your client + * {@code query} contains relevant placeholder(s). + *

* Created by raymo on 23/01/2017. */ @Slf4j @@ -26,14 +36,18 @@ public class ConnectionHandler { public static final int PORT_RANGE_END = 65534; private ServerSocketChannel channel; - @Getter - private InetAddress ipAddress; + @Getter private InetAddress ipAddress; private Thread ipFetcherThread; private static final String[] IP_PROVIDERS = new String[]{ + "http://whatismyip.akamai.com", + "http://ipecho.net/plain", "http://ip.tyk.nu/", "http://l2.io/ip", "http://ident.me/", - "http://icanhazip.com/" + "http://icanhazip.com/", + "https://api.ipify.org", + "https://ipinfo.io/ip", + "https://checkip.amazonaws.com" }; public int getPort() { @@ -42,22 +56,21 @@ public class ConnectionHandler { public void start() throws IOException { this.channel = this.bindToPort(); - final int port = this.channel.socket().getLocalPort(); - log.info("Listening for incoming peer connections on port {}.", port); + log.info("Listening for incoming peer connections on port {}", getPort()); this.ipAddress = fetchIp(); - log.info("Ip reported to tracker will be: {}", this.getIpAddress().getHostAddress()); + log.info("IP reported to tracker will be: {}", this.getIpAddress().getHostAddress()); + // TODO: use @Scheduled this.ipFetcherThread = new Thread(() -> { while (this.ipFetcherThread == null || !this.ipFetcherThread.isInterrupted()) { try { - // Sleep for one hour and a half. - Thread.sleep(1000 * 5400); + MINUTES.sleep(90); // TODO: move to config this.ipAddress = this.fetchIp(); } catch (final UnknownHostException e) { - log.warn("Faield to fetch Ip", e); + log.warn("Failed to fetch external IP", e); } catch (final InterruptedException e) { - log.info("Ip fetcher thread has been stopped."); + log.info("IP fetcher thread has been stopped"); } } }); @@ -68,28 +81,28 @@ public class ConnectionHandler { @VisibleForTesting InetAddress readIpFromProvider(final String providerUrl) throws IOException { final URLConnection urlConnection = new URL(providerUrl).openConnection(); - urlConnection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"); + urlConnection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"); // TODO: move to config try (final BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), Charsets.UTF_8))) { return InetAddress.getByName(in.readLine()); } finally { // Ensure all streams associated with http connection are closed - final InputStream err = ((HttpURLConnection) urlConnection).getErrorStream(); - try { if (err != null) err.close(); } + final InputStream errStream = ((HttpURLConnection) urlConnection).getErrorStream(); + try { if (errStream != null) errStream.close(); } catch (final IOException ignored) {} } } @VisibleForTesting Optional tryToFetchFromProviders() { - final List shuffledList = Lists.newArrayList(IP_PROVIDERS); + final List shuffledList = Arrays.asList(IP_PROVIDERS); Collections.shuffle(shuffledList); - for (final String ipProvider : shuffledList) { - log.info("Fetching ip from: " + ipProvider); + for (final String ipProviderUrl : shuffledList) { + log.info("Fetching ip from {}", ipProviderUrl); try { - return Optional.of(this.readIpFromProvider(ipProvider)); + return Optional.of(this.readIpFromProvider(ipProviderUrl)); } catch (final IOException e) { - log.warn("Failed to fetch Ip from [" + ipProvider + "]", e); + log.warn("Failed to fetch IP from [" + ipProviderUrl + "]", e); } } @@ -100,13 +113,13 @@ public class ConnectionHandler { InetAddress fetchIp() throws UnknownHostException { final Optional ip = this.tryToFetchFromProviders(); if (ip.isPresent()) { - log.info("Successfully fetch public IP address: {}", ip.get().getHostAddress()); + log.info("Successfully fetched public IP address: [{}]", ip.get().getHostAddress()); return ip.get(); - } - if (this.ipAddress != null) { - log.warn("Failed to fetch public IP address, reuse last known IP address: {}", this.ipAddress.getHostAddress()); + } else if (this.ipAddress != null) { + log.warn("Failed to fetch public IP address, reusing last known IP address: [{}]", this.ipAddress.getHostAddress()); return this.ipAddress; } + log.warn("Failed to fetch public IP address, fallback to localhost"); return InetAddress.getLocalHost(); } @@ -126,7 +139,8 @@ public class ConnectionHandler { break; } catch (final IOException ioe) { // Ignore, try next port - log.warn("Could not bind to port {}: {}, trying next port...", tryAddress.getPort(), ioe.getMessage()); + log.warn("Could not bind to port {}: {}", tryAddress.getPort(), ioe.getMessage()); + log.warn("trying next port..."); try { if (channel != null) channel.close(); } catch (final IOException ignored) { @@ -141,16 +155,17 @@ public class ConnectionHandler { } public void close() { - log.debug("Call to close ConnectionHandler."); + log.debug("Closing ConnectionHandler..."); try { if (this.channel != null) { this.channel.close(); } } catch (final Exception e) { - log.warn("ConnectionHandler channel has failed to release channel, but the shutdown will proceed.", e); + log.warn("ConnectionHandler channel has failed to release channel, but the shutdown will proceed", e); } finally { this.channel = null; } + try { if (this.ipFetcherThread != null) { this.ipFetcherThread.interrupt(); @@ -158,7 +173,6 @@ public class ConnectionHandler { } finally { this.ipFetcherThread = null; } - log.debug("ConnectionHandler closed."); + log.debug("ConnectionHandler closed"); } - } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/DelayQueue.java b/src/main/java/org/araymond/joal/core/ttorrent/client/DelayQueue.java index 0b60f13..ff799ef 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/DelayQueue.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/DelayQueue.java @@ -9,6 +9,9 @@ import java.time.temporal.TemporalUnit; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Predicate; + +import static java.util.Collections.emptyList; public class DelayQueue { private final Lock lock = new ReentrantLock(); @@ -28,26 +31,29 @@ public class DelayQueue { ); this.lock.lock(); try { - this.queue.removeIf(i -> i.getItem().getInfoHash().equals(item.getInfoHash())); // Ensure no double will be present in the queue (don't ant to have two announce type for a torrent) + this.queue.removeIf(infoHashEquals(item)); // Ensure no double will be present in the queue (don't want to have two announce type for a torrent) this.queue.add(intervalAware); } finally { this.lock.unlock(); } } + /** + * Get list of requests that are ready to be executed. + */ public List getAvailables() { this.lock.lock(); try { final IntervalAware first = queue.peek(); final LocalDateTime now = LocalDateTime.now(); if (first == null || first.releaseAt.isAfter(now)) { - return Collections.emptyList(); + return emptyList(); } final List timedOutItems = new ArrayList<>(); do { timedOutItems.add(this.queue.poll().getItem()); - } while (this.queue.size() > 0 && !this.queue.peek().releaseAt.isAfter(now)); + } while (!this.queue.isEmpty() && !this.queue.peek().releaseAt.isAfter(now)); return timedOutItems; } finally { @@ -55,10 +61,14 @@ public class DelayQueue { } } + private Predicate> infoHashEquals(T item) { + return i -> i.getItem().getInfoHash().equals(item.getInfoHash()); + } + public void remove(final T itemToRemove) { this.lock.lock(); try { - this.queue.removeIf(item -> item.getItem().getInfoHash().equals(itemToRemove.getInfoHash())); + this.queue.removeIf(infoHashEquals(itemToRemove)); } finally { this.lock.unlock(); } @@ -68,7 +78,7 @@ public class DelayQueue { this.lock.lock(); try { final List items = new ArrayList<>(queue.size()); - while (queue.size() > 0) { + while (!queue.isEmpty()) { items.add(queue.poll().getItem()); } return items; @@ -77,11 +87,9 @@ public class DelayQueue { } } - @RequiredArgsConstructor private static final class IntervalAware implements Comparable { - @Getter - private final T item; + @Getter private final T item; private final LocalDateTime releaseAt; @Override diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/Announcer.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/Announcer.java index aaf5f79..872c32c 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/Announcer.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/Announcer.java @@ -9,7 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.http.client.HttpClient; import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.torrent.torrent.MockedTorrent; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceDataAccessor; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; import org.araymond.joal.core.ttorrent.client.announcer.tracker.TrackerClient; @@ -21,19 +21,18 @@ import java.time.LocalDateTime; import java.util.Collection; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; + +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; @Slf4j public class Announcer implements AnnouncerFacade { - @Getter - private int lastKnownInterval = 5; - @Getter - private int consecutiveFails = 0; + @Getter private int lastKnownInterval = 5; + @Getter private int consecutiveFails; private Integer lastKnownLeechers = null; private Integer lastKnownSeeders = null; private LocalDateTime lastAnnouncedAt = null; - @Getter - private final MockedTorrent torrent; + @Getter private final MockedTorrent torrent; private TrackerClient trackerClient; private final AnnounceDataAccessor announceDataAccessor; @@ -47,7 +46,7 @@ public class Announcer implements AnnouncerFacade { final List trackerURIs = torrent.getAnnounceList().stream() // Use a list to keep it ordered .sequential() .flatMap(Collection::stream) - .collect(Collectors.toList()); + .collect(toList()); return new TrackerClient(new TrackerClientUriProvider(trackerURIs), new TrackerResponseHandler(), httpClient); } @@ -56,10 +55,8 @@ public class Announcer implements AnnouncerFacade { this.trackerClient = trackerClient; } - public SuccessAnnounceResponse announce(final RequestEvent event) throws AnnounceException, TooMuchAnnouncesFailedInARawException { - if (log.isDebugEnabled()) { - log.debug("Attempt to announce {} for {}", event.getEventName(), this.torrent.getTorrentInfoHash().getHumanReadable()); - } + public SuccessAnnounceResponse announce(final RequestEvent event) throws AnnounceException, TooManyAnnouncesFailedInARowException { + log.debug("Attempt to announce {} for {}", event.getEventName(), this.torrent.getTorrentInfoHash().getHumanReadable()); try { this.lastAnnouncedAt = LocalDateTime.now(); @@ -67,45 +64,41 @@ public class Announcer implements AnnouncerFacade { this.announceDataAccessor.getHttpRequestQueryForTorrent(this.torrent.getTorrentInfoHash(), event), this.announceDataAccessor.getHttpHeadersForTorrent() ); - if (log.isInfoEnabled()) { - log.info("{} has announced successfully. Response: {} seeders, {} leechers, {}s interval", this.torrent.getTorrentInfoHash().getHumanReadable(), responseMessage.getSeeders(), responseMessage.getLeechers(), responseMessage.getInterval()); - } + log.info("{} has announced successfully. Response: {} seeders, {} leechers, {}s interval", + this.torrent.getTorrentInfoHash().getHumanReadable(), responseMessage.getSeeders(), responseMessage.getLeechers(), responseMessage.getInterval()); this.lastKnownInterval = responseMessage.getInterval(); this.lastKnownLeechers = responseMessage.getLeechers(); this.lastKnownSeeders = responseMessage.getSeeders(); - this.consecutiveFails = 0; + this.consecutiveFails = 0; // reset failure tally return responseMessage; } catch (final Exception e) { - if (log.isWarnEnabled()) { - log.warn("{} has failed to announce", this.torrent.getTorrentInfoHash().getHumanReadable(), e); + this.consecutiveFails++; + if (this.consecutiveFails >= 5) { // TODO: move to config + log.warn("[{}] has failed to announce {} times in a row", this.torrent.getTorrentInfoHash().getHumanReadable(), this.consecutiveFails); + throw new TooManyAnnouncesFailedInARowException(torrent); + } else { + log.info("[{}] has failed to announce {}. time", this.torrent.getTorrentInfoHash().getHumanReadable(), this.consecutiveFails); } - ++this.consecutiveFails; - if (this.consecutiveFails >= 5) { - if (log.isInfoEnabled()) { - log.info("{} has failed to announce 5 times in a raw", this.torrent.getTorrentInfoHash().getHumanReadable()); - } - throw new TooMuchAnnouncesFailedInARawException(torrent); - } throw e; } } @Override public Optional getLastKnownLeechers() { - return Optional.ofNullable(lastKnownLeechers); + return ofNullable(lastKnownLeechers); } @Override public Optional getLastKnownSeeders() { - return Optional.ofNullable(lastKnownSeeders); + return ofNullable(lastKnownSeeders); } @Override public Optional getLastAnnouncedAt() { - return Optional.ofNullable(lastAnnouncedAt); + return ofNullable(lastAnnouncedAt); } @Override @@ -123,14 +116,19 @@ public class Announcer implements AnnouncerFacade { return this.getTorrent().getTorrentInfoHash(); } + /** + * Make sure to keep {@code torrentInfoHash} as the only input. + */ @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - final Announcer announcer = (Announcer) o; - return Objects.equal(this.getTorrentInfoHash(), announcer.getTorrentInfoHash()); + return Objects.equal(this.getTorrentInfoHash(), ((Announcer) o).getTorrentInfoHash()); } + /** + * Make sure to keep {@code torrentInfoHash} as the only input. + */ @Override public int hashCode() { return this.getTorrentInfoHash().hashCode(); diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooMuchAnnouncesFailedInARawException.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooManyAnnouncesFailedInARowException.java similarity index 73% rename from src/main/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooMuchAnnouncesFailedInARawException.java rename to src/main/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooManyAnnouncesFailedInARowException.java index 069287a..c64994e 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooMuchAnnouncesFailedInARawException.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooManyAnnouncesFailedInARowException.java @@ -4,12 +4,12 @@ import lombok.Getter; import org.araymond.joal.core.torrent.torrent.MockedTorrent; @Getter -public class TooMuchAnnouncesFailedInARawException extends Exception { +public class TooManyAnnouncesFailedInARowException extends Exception { private static final long serialVersionUID = 1864953989056739188L; private final MockedTorrent torrent; - public TooMuchAnnouncesFailedInARawException(final MockedTorrent torrent) { + public TooManyAnnouncesFailedInARowException(final MockedTorrent torrent) { super(); this.torrent = torrent; } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessor.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessor.java index a4d077c..ec2dc98 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessor.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessor.java @@ -7,8 +7,8 @@ import org.araymond.joal.core.client.emulated.BitTorrentClient; import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.ttorrent.client.ConnectionHandler; -import java.util.List; import java.util.Map; +import java.util.Set; @RequiredArgsConstructor public class AnnounceDataAccessor { @@ -21,7 +21,7 @@ public class AnnounceDataAccessor { return this.bitTorrentClient.createRequestQuery(event, infoHash, this.bandwidthDispatcher.getSeedStatForTorrent(infoHash), this.connectionHandler); } - public List> getHttpHeadersForTorrent() { - return this.bitTorrentClient.createRequestHeaders(); + public Set> getHttpHeadersForTorrent() { + return this.bitTorrentClient.getHeaders(); } } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceRequest.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceRequest.java index 6d93e02..9e261fc 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceRequest.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceRequest.java @@ -26,6 +26,10 @@ public final class AnnounceRequest implements DelayQueue.InfoHashAble { return new AnnounceRequest(announcer, RequestEvent.STOPPED); } + public AnnounceRequest toStop() { + return createStop(announcer); + } + @Override public InfoHash getInfoHash() { return this.announcer.getTorrentInfoHash(); diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnouncerExecutor.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnouncerExecutor.java index 0be3dca..df895be 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnouncerExecutor.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnouncerExecutor.java @@ -6,12 +6,15 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.response.AnnounceResponseCallback; import java.util.*; import java.util.concurrent.*; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toSet; + @Slf4j public class AnnouncerExecutor { @@ -29,69 +32,53 @@ public class AnnouncerExecutor { final int corePoolSize = 3; final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("annnouncer-%d").build(); this.executorService = new ThreadPoolExecutor(corePoolSize, 3, 40, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), threadFactory); - this.currentlyRunning = new HashMap<>(corePoolSize); + this.currentlyRunning = new ConcurrentHashMap<>(); } public void execute(final AnnounceRequest request) { - final Callable callable = () -> { + final Runnable task = () -> { try { announceResponseCallback.onAnnounceWillAnnounce(request.getEvent(), request.getAnnouncer()); final SuccessAnnounceResponse result = request.getAnnouncer().announce(request.getEvent()); announceResponseCallback.onAnnounceSuccess(request.getEvent(), request.getAnnouncer(), result); - } catch (final TooMuchAnnouncesFailedInARawException e) { - announceResponseCallback.onTooManyAnnounceFailedInARaw(request.getEvent(), request.getAnnouncer(), e); + } catch (final TooManyAnnouncesFailedInARowException e) { + announceResponseCallback.onTooManyAnnounceFailedInARow(request.getEvent(), request.getAnnouncer(), e); } catch (final Throwable throwable) { announceResponseCallback.onAnnounceFailure(request.getEvent(), request.getAnnouncer(), throwable); } finally { this.currentlyRunning.remove(request.getAnnouncer().getTorrentInfoHash()); } - return null; }; - final Future future = this.executorService.submit(callable); - + final Future taskFuture = this.executorService.submit(task); this.currentlyRunning.put( request.getAnnouncer().getTorrentInfoHash(), - new AnnouncerWithFuture( - request.getAnnouncer(), - future - ) + new AnnouncerWithFuture(request.getAnnouncer(), taskFuture) ); } public Optional deny(final InfoHash infoHash) { - final AnnouncerWithFuture announcerWithFuture = this.currentlyRunning.get(infoHash); - if (announcerWithFuture == null) { - return Optional.empty(); - } - announcerWithFuture.getFuture().cancel(true); - this.currentlyRunning.remove(infoHash); - - return Optional.of(announcerWithFuture.getAnnouncer()); + return ofNullable(this.currentlyRunning.remove(infoHash)).map(announcerFuture -> { + announcerFuture.getFuture().cancel(true); + return announcerFuture.getAnnouncer(); + }); } - public List denyAll() { - final Set infoHashes = new HashSet<>(this.currentlyRunning.keySet()); - final List announcersCanceled = new ArrayList<>(); - - for (final InfoHash infoHash: infoHashes) { - final AnnouncerWithFuture announcerWithFuture = this.currentlyRunning.get(infoHash); - if (announcerWithFuture != null) { - announcerWithFuture.getFuture().cancel(true); - this.currentlyRunning.remove(infoHash); - announcersCanceled.add(announcerWithFuture.getAnnouncer()); - } - } - - return announcersCanceled; + public Set denyAll() { + return new HashSet<>(this.currentlyRunning.keySet()).stream() + .map(this::deny) + .flatMap(Optional::stream) + .collect(toSet()); } public void awaitForRunningTasks() { this.executorService.shutdown(); try { - this.executorService.awaitTermination(10, TimeUnit.SECONDS); + if (!this.executorService.awaitTermination(10, TimeUnit.SECONDS)) { + log.warn("AnnouncerExecutor timed out after 10s"); + } } catch (final InterruptedException e) { - log.warn("AnnouncerExecutor has ended with timeout, some torrents was still trying to announce after 10s", e); + log.warn("AnnouncerExecutor interrupt", e); } } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceEventPublisher.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceEventPublisher.java index d5b0385..2da9ab2 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceEventPublisher.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceEventPublisher.java @@ -8,61 +8,60 @@ import org.araymond.joal.core.events.announce.SuccessfullyAnnounceEvent; import org.araymond.joal.core.events.announce.TooManyAnnouncesFailedEvent; import org.araymond.joal.core.events.announce.WillAnnounceEvent; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; import org.springframework.context.ApplicationEventPublisher; @RequiredArgsConstructor @Slf4j -public class AnnounceEventPublisher implements AnnounceResponseHandlerChainElement { +public class AnnounceEventPublisher implements AnnounceResponseHandler { private final ApplicationEventPublisher eventPublisher; @Override public void onAnnouncerWillAnnounce(final Announcer announcer, final RequestEvent event) { - log.debug("Publish WillAnnounceEvent event for {}.", announcer.getTorrentInfoHash().getHumanReadable()); + log.debug("Publish WillAnnounceEvent event for [{}]", announcer.getTorrentInfoHash().getHumanReadable()); this.eventPublisher.publishEvent(new WillAnnounceEvent(announcer, event)); } @Override public void onAnnounceStartSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { - log.debug("Publish SuccessfullyAnnounceEvent event for {}.", announcer.getTorrentInfoHash().getHumanReadable()); + log.debug("Publish SuccessfullyAnnounceEvent event for [{}]", announcer.getTorrentInfoHash().getHumanReadable()); this.eventPublisher.publishEvent(new SuccessfullyAnnounceEvent(announcer, RequestEvent.STARTED)); } @Override public void onAnnounceStartFails(final Announcer announcer, final Throwable throwable) { - log.debug("Publish FailedToAnnounceEvent event for {}.", announcer.getTorrentInfoHash().getHumanReadable()); + log.debug("Publish FailedToAnnounceEvent event for [{}]", announcer.getTorrentInfoHash().getHumanReadable()); this.eventPublisher.publishEvent(new FailedToAnnounceEvent(announcer, RequestEvent.STARTED, throwable.getMessage())); } @Override public void onAnnounceRegularSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { - log.debug("Publish SuccessfullyAnnounceEvent event for {}.", announcer.getTorrentInfoHash().getHumanReadable()); + log.debug("Publish SuccessfullyAnnounceEvent event for [{}]", announcer.getTorrentInfoHash().getHumanReadable()); this.eventPublisher.publishEvent(new SuccessfullyAnnounceEvent(announcer, RequestEvent.NONE)); } @Override public void onAnnounceRegularFails(final Announcer announcer, final Throwable throwable) { - log.debug("Publish FailedToAnnounceEvent event for {}.", announcer.getTorrentInfoHash().getHumanReadable()); + log.debug("Publish FailedToAnnounceEvent event for [{}]", announcer.getTorrentInfoHash().getHumanReadable()); this.eventPublisher.publishEvent(new FailedToAnnounceEvent(announcer, RequestEvent.NONE, throwable.getMessage())); } @Override public void onAnnounceStopSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { - log.debug("Publish SuccessfullyAnnounceEvent event for {}.", announcer.getTorrentInfoHash().getHumanReadable()); + log.debug("Publish SuccessfullyAnnounceEvent event for [{}]", announcer.getTorrentInfoHash().getHumanReadable()); this.eventPublisher.publishEvent(new SuccessfullyAnnounceEvent(announcer, RequestEvent.STOPPED)); } @Override public void onAnnounceStopFails(final Announcer announcer, final Throwable throwable) { - log.debug("Publish FailedToAnnounceEvent event for {}.", announcer.getTorrentInfoHash().getHumanReadable()); + log.debug("Publish FailedToAnnounceEvent event for [{}]", announcer.getTorrentInfoHash().getHumanReadable()); this.eventPublisher.publishEvent(new FailedToAnnounceEvent(announcer, RequestEvent.STOPPED, throwable.getMessage())); } @Override - public void onTooManyAnnounceFailedInARaw(final Announcer announcer, final TooMuchAnnouncesFailedInARawException e) { - log.debug("Publish TooManyAnnouncesFailedEvent event for {}.", announcer.getTorrentInfoHash().getHumanReadable()); + public void onTooManyAnnounceFailedInARow(final Announcer announcer, final TooManyAnnouncesFailedInARowException e) { + log.debug("Publish TooManyAnnouncesFailedEvent event for [{}]", announcer.getTorrentInfoHash().getHumanReadable()); this.eventPublisher.publishEvent(new TooManyAnnouncesFailedEvent(announcer)); } - } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceReEnqueuer.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceReEnqueuer.java index b619dce..06e80d4 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceReEnqueuer.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceReEnqueuer.java @@ -5,56 +5,61 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.araymond.joal.core.ttorrent.client.DelayQueue; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceRequest; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; import java.time.temporal.ChronoUnit; +import static org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceRequest.*; + @RequiredArgsConstructor @Slf4j -public class AnnounceReEnqueuer implements AnnounceResponseHandlerChainElement { +public class AnnounceReEnqueuer implements AnnounceResponseHandler { private final DelayQueue delayQueue; @Override public void onAnnouncerWillAnnounce(final Announcer announcer, final RequestEvent event) { + // noop } @Override public void onAnnounceStartSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { - log.debug("Enqueue torrent {} in regular queue.", announcer.getTorrentInfoHash().getHumanReadable()); - this.delayQueue.addOrReplace(AnnounceRequest.createRegular(announcer), result.getInterval(), ChronoUnit.SECONDS); + log.debug("Enqueue torrent {} in regular queue", announcer.getTorrentInfoHash().getHumanReadable()); + this.delayQueue.addOrReplace(createRegular(announcer), result.getInterval(), ChronoUnit.SECONDS); } @Override public void onAnnounceStartFails(final Announcer announcer, final Throwable throwable) { - log.debug("Enqueue torrent {} in start queue once again (because it failed).", announcer.getTorrentInfoHash().getHumanReadable()); - this.delayQueue.addOrReplace(AnnounceRequest.createStart(announcer), announcer.getLastKnownInterval(), ChronoUnit.SECONDS); + log.debug("Enqueue torrent {} in start queue once again (because it failed)", announcer.getTorrentInfoHash().getHumanReadable()); + this.delayQueue.addOrReplace(createStart(announcer), announcer.getLastKnownInterval(), ChronoUnit.SECONDS); } @Override public void onAnnounceRegularSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { - log.debug("Enqueue torrent {} in regular queue.", announcer.getTorrentInfoHash().getHumanReadable()); - this.delayQueue.addOrReplace(AnnounceRequest.createRegular(announcer), result.getInterval(), ChronoUnit.SECONDS); + log.debug("Enqueue torrent {} in regular queue", announcer.getTorrentInfoHash().getHumanReadable()); + this.delayQueue.addOrReplace(createRegular(announcer), result.getInterval(), ChronoUnit.SECONDS); } @Override public void onAnnounceRegularFails(final Announcer announcer, final Throwable throwable) { - log.debug("Enqueue torrent {} in regular queue once again (because it failed).", announcer.getTorrentInfoHash().getHumanReadable()); - this.delayQueue.addOrReplace(AnnounceRequest.createRegular(announcer), announcer.getLastKnownInterval(), ChronoUnit.SECONDS); + log.debug("Enqueue torrent {} in regular queue once again (because it failed)", announcer.getTorrentInfoHash().getHumanReadable()); + this.delayQueue.addOrReplace(createRegular(announcer), announcer.getLastKnownInterval(), ChronoUnit.SECONDS); } @Override public void onAnnounceStopSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { + // noop } @Override public void onAnnounceStopFails(final Announcer announcer, final Throwable throwable) { - log.debug("Enqueue torrent {} in stop queue once again (because it failed).", announcer.getTorrentInfoHash().getHumanReadable()); - this.delayQueue.addOrReplace(AnnounceRequest.createStop(announcer), 0, ChronoUnit.SECONDS); + log.debug("Enqueue torrent {} in stop queue once again (because it failed)", announcer.getTorrentInfoHash().getHumanReadable()); + this.delayQueue.addOrReplace(createStop(announcer), 0, ChronoUnit.SECONDS); } @Override - public void onTooManyAnnounceFailedInARaw(final Announcer announcer, final TooMuchAnnouncesFailedInARawException e) { + public void onTooManyAnnounceFailedInARow(final Announcer announcer, final TooManyAnnouncesFailedInARowException e) { + // noop } } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseCallback.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseCallback.java index 7649c36..7f8bc4a 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseCallback.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseCallback.java @@ -2,12 +2,12 @@ package org.araymond.joal.core.ttorrent.client.announcer.response; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; public interface AnnounceResponseCallback { void onAnnounceWillAnnounce(RequestEvent event, Announcer announcer); void onAnnounceSuccess(RequestEvent event, Announcer announcer, SuccessAnnounceResponse result); void onAnnounceFailure(RequestEvent event, Announcer announcer, Throwable throwable); - void onTooManyAnnounceFailedInARaw(RequestEvent event, Announcer announcer, TooMuchAnnouncesFailedInARawException e); + void onTooManyAnnounceFailedInARow(RequestEvent event, Announcer announcer, TooManyAnnouncesFailedInARowException e); } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChainElement.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandler.java similarity index 81% rename from src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChainElement.java rename to src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandler.java index a365793..7a4c7b0 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChainElement.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandler.java @@ -2,10 +2,10 @@ package org.araymond.joal.core.ttorrent.client.announcer.response; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; -public interface AnnounceResponseHandlerChainElement { +public interface AnnounceResponseHandler { void onAnnouncerWillAnnounce(Announcer announcer, RequestEvent event); void onAnnounceStartSuccess(Announcer announcer, SuccessAnnounceResponse result); void onAnnounceStartFails(Announcer announcer, Throwable throwable); @@ -13,5 +13,5 @@ public interface AnnounceResponseHandlerChainElement { void onAnnounceRegularFails(Announcer announcer, Throwable throwable); void onAnnounceStopSuccess(Announcer announcer, SuccessAnnounceResponse result); void onAnnounceStopFails(Announcer announcer, Throwable throwable); - void onTooManyAnnounceFailedInARaw(Announcer announcer, TooMuchAnnouncesFailedInARawException e); + void onTooManyAnnounceFailedInARow(Announcer announcer, TooManyAnnouncesFailedInARowException e); } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChain.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChain.java index c56ebc6..9a27101 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChain.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChain.java @@ -3,83 +3,76 @@ package org.araymond.joal.core.ttorrent.client.announcer.response; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; import lombok.extern.slf4j.Slf4j; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; import java.util.ArrayList; import java.util.List; +// TODO: replace this custom logic with a message bus @Slf4j public class AnnounceResponseHandlerChain implements AnnounceResponseCallback { - private final List chainElements; + private final List handlers = new ArrayList<>(); - public AnnounceResponseHandlerChain() { - chainElements = new ArrayList<>(4); - } - - public void appendHandler(final AnnounceResponseHandlerChainElement element) { - this.chainElements.add(element); + public void appendHandler(final AnnounceResponseHandler element) { + this.handlers.add(element); } @Override public void onAnnounceWillAnnounce(final RequestEvent event, final Announcer announcer) { - for (final AnnounceResponseHandlerChainElement element : chainElements) { - element.onAnnouncerWillAnnounce(announcer, event); - } + handlers.forEach(e -> e.onAnnouncerWillAnnounce(announcer, event)); } @Override public void onAnnounceSuccess(final RequestEvent event, final Announcer announcer, final SuccessAnnounceResponse result) { - for (final AnnounceResponseHandlerChainElement element : chainElements) { + handlers.forEach(handler -> { switch (event) { case STARTED: { - element.onAnnounceStartSuccess(announcer, result); + handler.onAnnounceStartSuccess(announcer, result); break; } case NONE: { - element.onAnnounceRegularSuccess(announcer, result); + handler.onAnnounceRegularSuccess(announcer, result); break; } case STOPPED: { - element.onAnnounceStopSuccess(announcer, result); + handler.onAnnounceStopSuccess(announcer, result); break; } default: { - log.warn("Event {} cannot be handled by {}", event.getEventName(), getClass().getSimpleName()); + log.warn("Event [{}] cannot be handled by {}", event.getEventName(), getClass().getSimpleName()); break; } } - } + }); } @Override public void onAnnounceFailure(final RequestEvent event, final Announcer announcer, final Throwable throwable) { - for (final AnnounceResponseHandlerChainElement element : chainElements) { + handlers.forEach(handler -> { switch (event) { case STARTED: { - element.onAnnounceStartFails(announcer, throwable); + handler.onAnnounceStartFails(announcer, throwable); break; } case NONE: { - element.onAnnounceRegularFails(announcer, throwable); + handler.onAnnounceRegularFails(announcer, throwable); break; } case STOPPED: { - element.onAnnounceStopFails(announcer, throwable); + handler.onAnnounceStopFails(announcer, throwable); break; } default: { - log.warn("Event {} cannot be handled by {}", event.getEventName(), getClass().getSimpleName()); + log.warn("Event [{}] cannot be handled by {}", event.getEventName(), getClass().getSimpleName()); break; } } - } + }); } @Override - public void onTooManyAnnounceFailedInARaw(final RequestEvent event, final Announcer announcer, final TooMuchAnnouncesFailedInARawException e) { - for (final AnnounceResponseHandlerChainElement chainElement : chainElements) { - chainElement.onTooManyAnnounceFailedInARaw(announcer, e); - } + public void onTooManyAnnounceFailedInARow(final RequestEvent event, final Announcer announcer, final TooManyAnnouncesFailedInARowException e) { + handlers.forEach(h -> h.onTooManyAnnounceFailedInARow(announcer, e)); } } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/BandwidthDispatcherNotifier.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/BandwidthDispatcherNotifier.java index 1e6c916..c783e74 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/BandwidthDispatcherNotifier.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/BandwidthDispatcherNotifier.java @@ -6,21 +6,22 @@ import lombok.extern.slf4j.Slf4j; import org.araymond.joal.core.bandwith.BandwidthDispatcher; import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; @RequiredArgsConstructor @Slf4j -public class BandwidthDispatcherNotifier implements AnnounceResponseHandlerChainElement { +public class BandwidthDispatcherNotifier implements AnnounceResponseHandler { private final BandwidthDispatcher bandwidthDispatcher; @Override public void onAnnouncerWillAnnounce(final Announcer announcer, final RequestEvent event) { + // noop } @Override public void onAnnounceStartSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { - log.debug("Register {} in bandwidth dispatcher and update stats.", announcer.getTorrentInfoHash().getHumanReadable()); + log.debug("Register [{}] in bandwidth dispatcher and update stats", announcer.getTorrentInfoHash().getHumanReadable()); final InfoHash infoHash = announcer.getTorrentInfoHash(); this.bandwidthDispatcher.registerTorrent(infoHash); this.bandwidthDispatcher.updateTorrentPeers(infoHash, result.getSeeders(), result.getLeechers()); @@ -28,32 +29,35 @@ public class BandwidthDispatcherNotifier implements AnnounceResponseHandlerChain @Override public void onAnnounceStartFails(final Announcer announcer, final Throwable throwable) { + // noop } @Override public void onAnnounceRegularSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { - log.debug("Update {} stats in bandwidth dispatcher.", announcer.getTorrentInfoHash().getHumanReadable()); + log.debug("Update [{}] stats in bandwidth dispatcher", announcer.getTorrentInfoHash().getHumanReadable()); final InfoHash infoHash = announcer.getTorrentInfoHash(); this.bandwidthDispatcher.updateTorrentPeers(infoHash, result.getSeeders(), result.getLeechers()); } @Override public void onAnnounceRegularFails(final Announcer announcer, final Throwable throwable) { + // noop } @Override public void onAnnounceStopSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { - log.debug("Unregister {} from bandwidth dispatcher.", announcer.getTorrentInfoHash().getHumanReadable()); + log.debug("Unregister [{}] from bandwidth dispatcher", announcer.getTorrentInfoHash().getHumanReadable()); this.bandwidthDispatcher.unregisterTorrent(announcer.getTorrentInfoHash()); } @Override public void onAnnounceStopFails(final Announcer announcer, final Throwable throwable) { + // noop } @Override - public void onTooManyAnnounceFailedInARaw(final Announcer announcer, final TooMuchAnnouncesFailedInARawException e) { - log.debug("Unregister {} from bandwidth dispatcher.", announcer.getTorrentInfoHash().getHumanReadable()); + public void onTooManyAnnounceFailedInARow(final Announcer announcer, final TooManyAnnouncesFailedInARowException e) { + log.debug("Unregister [{}] from bandwidth dispatcher", announcer.getTorrentInfoHash().getHumanReadable()); this.bandwidthDispatcher.unregisterTorrent(announcer.getTorrentInfoHash()); } } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifier.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifier.java index 481099e..f4deaf9 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifier.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifier.java @@ -1,22 +1,21 @@ package org.araymond.joal.core.ttorrent.client.announcer.response; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.araymond.joal.core.ttorrent.client.Client; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; @Slf4j -public class ClientNotifier implements AnnounceResponseHandlerChainElement { - private Client client; - - public void setClient(final Client client) { - this.client = client; - } +@RequiredArgsConstructor +public class ClientNotifier implements AnnounceResponseHandler { + private final Client client; @Override public void onAnnouncerWillAnnounce(final Announcer announcer, final RequestEvent event) { + // noop } @Override @@ -28,6 +27,7 @@ public class ClientNotifier implements AnnounceResponseHandlerChainElement { @Override public void onAnnounceStartFails(final Announcer announcer, final Throwable throwable) { + // noop } @Override @@ -39,21 +39,23 @@ public class ClientNotifier implements AnnounceResponseHandlerChainElement { @Override public void onAnnounceRegularFails(final Announcer announcer, final Throwable throwable) { + // noop } @Override public void onAnnounceStopSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { - log.debug("Notify client that a torrent has stopped."); + log.debug("Notify client that a torrent has stopped"); this.client.onTorrentHasStopped(announcer); } @Override public void onAnnounceStopFails(final Announcer announcer, final Throwable throwable) { + // noop } @Override - public void onTooManyAnnounceFailedInARaw(final Announcer announcer, final TooMuchAnnouncesFailedInARawException e) { - log.debug("Notify client that a torrent has failed too many times."); - this.client.onTooManyFailedInARaw(announcer); + public void onTooManyAnnounceFailedInARow(final Announcer announcer, final TooManyAnnouncesFailedInARowException e) { + log.debug("Notify client that a torrent has failed too many times"); + this.client.onTooManyFailedInARow(announcer); } } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClient.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClient.java index 66d7f91..f508b3b 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClient.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClient.java @@ -5,6 +5,7 @@ import com.turn.ttorrent.client.announce.AnnounceException; import com.turn.ttorrent.common.protocol.TrackerMessage; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceResponseMessage; import com.turn.ttorrent.common.protocol.TrackerMessage.ErrorMessage; +import lombok.RequiredArgsConstructor; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; @@ -13,35 +14,23 @@ import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; +import org.springframework.http.HttpHeaders; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.Map; +@RequiredArgsConstructor public class TrackerClient { private final TrackerClientUriProvider trackerClientUriProvider; - private final HttpClient httpClient; private final ResponseHandler trackerResponseHandler; - - public TrackerClient(final TrackerClientUriProvider trackerClientUriProvider, final ResponseHandler trackerResponseHandler, final HttpClient httpClient) { - this.trackerResponseHandler = trackerResponseHandler; - this.trackerClientUriProvider = trackerClientUriProvider; - this.httpClient = httpClient; - } + private final HttpClient httpClient; public SuccessAnnounceResponse announce(final String requestQuery, final Iterable> headers) throws AnnounceException { - final URI baseUri; - try { - while (!this.trackerClientUriProvider.get().getScheme().startsWith("http")) { - this.trackerClientUriProvider.deleteCurrentAndMoveToNext(); - } - baseUri = this.trackerClientUriProvider.get(); - } catch (final NoMoreUriAvailableException e) { - throw new AnnounceException("No more valid tracker URI", e); - } - + final URI baseUri = this.trackerClientUriProvider.get(); final TrackerMessage responseMessage; + try { responseMessage = this.makeCallAndGetResponseAsByteBuffer(baseUri, requestQuery, headers); @@ -54,44 +43,43 @@ public class TrackerClient { try { this.trackerClientUriProvider.moveToNext(); } catch (final NoMoreUriAvailableException e1) { - throw new AnnounceException("No more valid tracker for torrent.", e1); + throw new AnnounceException("No more valid tracker for torrent", e1); } throw new AnnounceException(e.getMessage(), e); } if (!(responseMessage instanceof AnnounceResponseMessage)) { - throw new AnnounceException("Unexpected tracker message type " + responseMessage.getType().name() + "!"); + throw new AnnounceException("Unexpected tracker message type [" + responseMessage.getType().name() + "]!"); } final AnnounceResponseMessage announceResponseMessage = (AnnounceResponseMessage) responseMessage; final int interval = announceResponseMessage.getInterval(); - final int seeders = announceResponseMessage.getComplete() == 0 ? 0 : announceResponseMessage.getComplete() - 1; // Subtract one to seeders since we are one of them + final int seeders = Math.max(0, announceResponseMessage.getComplete() - 1); // -1 seeders since we are one of them; TODO: not the case while we're downloading though, right? not too sure about that.. final int leechers = announceResponseMessage.getIncomplete(); return new SuccessAnnounceResponse(interval, seeders, leechers); } @VisibleForTesting - TrackerMessage makeCallAndGetResponseAsByteBuffer(final URI announceUri, final String requestQuery, final Iterable> headers) throws AnnounceException { - final String base = announceUri + (announceUri.toString().contains("?") ? "&": "?"); + TrackerMessage makeCallAndGetResponseAsByteBuffer(final URI announceUri, final String requestQuery, + final Iterable> headers) throws AnnounceException { + final String base = announceUri + (announceUri.toString().contains("?") ? "&" : "?"); final HttpUriRequest request = new HttpGet(base + requestQuery); String host = announceUri.getHost(); if (announceUri.getPort() != -1) { host += ":" + announceUri.getPort(); } - request.addHeader("Host", host); - for (final Map.Entry entry : headers) { - request.addHeader(entry.getKey(), entry.getValue()); - } + request.addHeader(HttpHeaders.HOST, host); + headers.forEach(hdrEntry -> request.addHeader(hdrEntry.getKey(), hdrEntry.getValue())); final HttpResponse response; try { response = httpClient.execute(request); } catch (final ClientProtocolException e) { - throw new AnnounceException("Failed to announce: protocol mismatch.", e); + throw new AnnounceException("Failed to announce: protocol mismatch", e); } catch (final IOException e) { - throw new AnnounceException("Failed to announce: error or connection aborted.", e); + throw new AnnounceException("Failed to announce: error or connection aborted", e); } try { diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientUriProvider.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientUriProvider.java index 85c8881..80b835a 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientUriProvider.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientUriProvider.java @@ -1,39 +1,45 @@ package org.araymond.joal.core.ttorrent.client.announcer.tracker; import com.google.common.collect.Iterators; -import org.slf4j.LoggerFactory; +import lombok.SneakyThrows; import java.net.URI; import java.util.Iterator; import java.util.List; +import static java.util.stream.Collectors.toList; + public class TrackerClientUriProvider { private final Iterator addressIterator; private URI currentURI = null; + @SneakyThrows public TrackerClientUriProvider(@SuppressWarnings("TypeMayBeWeakened") final List trackersURI) { + List trackers = trackersURI.stream() + .filter(uri -> uri.getScheme().startsWith("http")) + .collect(toList()); + + if (trackers.isEmpty()) { + throw new NoMoreUriAvailableException("No valid http trackers provided"); + } + // TODO: sorted(new PreferHTTPSComparator()) - this.addressIterator = Iterators.cycle(trackersURI); + this.addressIterator = Iterators.cycle(trackers); + this.currentURI = this.addressIterator.next(); // initialize state } URI get() { - if (this.currentURI == null) { - this.currentURI = this.addressIterator.next(); - } return this.currentURI; } void deleteCurrentAndMoveToNext() throws NoMoreUriAvailableException { - if (this.currentURI == null) { - this.currentURI = this.addressIterator.next(); - } this.addressIterator.remove(); this.moveToNext(); } void moveToNext() throws NoMoreUriAvailableException { if (!this.addressIterator.hasNext()) { - throw new NoMoreUriAvailableException("No more valid trackers"); + throw new NoMoreUriAvailableException("No more valid tracker URIs left"); } this.currentURI = this.addressIterator.next(); } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerResponseHandler.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerResponseHandler.java index 29b2ae3..31d44c8 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerResponseHandler.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerResponseHandler.java @@ -25,7 +25,7 @@ public class TrackerResponseHandler implements ResponseHandler { final int contentLength = entity.getContentLength() < 1 ? 1024 : (int) entity.getContentLength(); try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(contentLength)) { if (response.getStatusLine().getStatusCode() >= 300) { - log.warn("Tracker response is an error."); + log.warn("Tracker response is an error: status {}", response.getStatusLine().getStatusCode()); } try { diff --git a/src/main/java/org/araymond/joal/web/config/BeanConfig.java b/src/main/java/org/araymond/joal/web/config/BeanConfig.java index 40d67f3..cb2c9cb 100644 --- a/src/main/java/org/araymond/joal/web/config/BeanConfig.java +++ b/src/main/java/org/araymond/joal/web/config/BeanConfig.java @@ -16,7 +16,8 @@ import java.io.IOException; public class BeanConfig { @Bean - public SeedManager seedManager(@Value("${joal-conf}") final String joalConfFolder, final ObjectMapper mapper, final ApplicationEventPublisher publisher) throws IOException { + public SeedManager seedManager(@Value("${joal-conf}") final String joalConfFolder, + final ObjectMapper mapper, final ApplicationEventPublisher publisher) throws IOException { return new SeedManager(joalConfFolder, mapper, publisher); } } diff --git a/src/main/java/org/araymond/joal/web/config/JacksonConfig.java b/src/main/java/org/araymond/joal/web/config/JacksonConfig.java index f804f3f..5b3dc18 100644 --- a/src/main/java/org/araymond/joal/web/config/JacksonConfig.java +++ b/src/main/java/org/araymond/joal/web/config/JacksonConfig.java @@ -14,6 +14,8 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; +import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS; + /** * Created by raymo on 30/06/2017. */ @@ -23,9 +25,7 @@ public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilder objectMapperBuilder() { return new Jackson2ObjectMapperBuilder() - .featuresToDisable( - SerializationFeature.WRITE_DATES_AS_TIMESTAMPS - ) + .featuresToDisable(WRITE_DATES_AS_TIMESTAMPS) .failOnEmptyBeans(false) .serializers( new InfoHashSerializer(), diff --git a/src/main/java/org/araymond/joal/web/config/obfuscation/AbortNonPrefixedRequestFilter.java b/src/main/java/org/araymond/joal/web/config/obfuscation/AbortNonPrefixedRequestFilter.java index f23ba49..7e8efae 100644 --- a/src/main/java/org/araymond/joal/web/config/obfuscation/AbortNonPrefixedRequestFilter.java +++ b/src/main/java/org/araymond/joal/web/config/obfuscation/AbortNonPrefixedRequestFilter.java @@ -21,7 +21,7 @@ import java.io.IOException; public class AbortNonPrefixedRequestFilter implements Filter { private final String pathPrefix; - public AbortNonPrefixedRequestFilter(@Value("${joal.ui.path.prefix}")final String pathPrefix) { + public AbortNonPrefixedRequestFilter(@Value("${joal.ui.path.prefix}") final String pathPrefix) { this.pathPrefix = pathPrefix; } @@ -34,7 +34,7 @@ public class AbortNonPrefixedRequestFilter implements Filter { requestedUri = requestedUri.substring(1); } if (!requestedUri.startsWith(pathPrefix)) { - log.warn("Request was sent to URI '{}' and does not match the path prefix, therefore the request Thread has been shut down.", req.getRequestURI()); + log.warn("Request was sent to URI [{}] and does not match the path prefix, therefore the request Thread has been shut down", req.getRequestURI()); Thread.currentThread().interrupt(); return; } diff --git a/src/main/java/org/araymond/joal/web/config/obfuscation/EndpointObfuscatorConfiguration.java b/src/main/java/org/araymond/joal/web/config/obfuscation/EndpointObfuscatorConfiguration.java index b482d82..961e345 100644 --- a/src/main/java/org/araymond/joal/web/config/obfuscation/EndpointObfuscatorConfiguration.java +++ b/src/main/java/org/araymond/joal/web/config/obfuscation/EndpointObfuscatorConfiguration.java @@ -18,7 +18,7 @@ public class EndpointObfuscatorConfiguration { private final String pathPrefix; - public EndpointObfuscatorConfiguration(@Value("${joal.ui.path.prefix}")final String pathPrefix) { + public EndpointObfuscatorConfiguration(@Value("${joal.ui.path.prefix}") final String pathPrefix) { this.pathPrefix = pathPrefix; } diff --git a/src/main/java/org/araymond/joal/web/messages/incoming/config/Base64TorrentIncomingMessage.java b/src/main/java/org/araymond/joal/web/messages/incoming/config/Base64TorrentIncomingMessage.java index 6d6f22f..c3c53c4 100644 --- a/src/main/java/org/araymond/joal/web/messages/incoming/config/Base64TorrentIncomingMessage.java +++ b/src/main/java/org/araymond/joal/web/messages/incoming/config/Base64TorrentIncomingMessage.java @@ -13,7 +13,8 @@ public class Base64TorrentIncomingMessage { private final String b64String; @JsonCreator - public Base64TorrentIncomingMessage(@JsonProperty("fileName") final String fileName, @JsonProperty("b64String") final String b64String) { + public Base64TorrentIncomingMessage(@JsonProperty("fileName") final String fileName, + @JsonProperty("b64String") final String b64String) { this.fileName = fileName; this.b64String = b64String; } diff --git a/src/main/java/org/araymond/joal/web/messages/outgoing/impl/speed/SeedingSpeedHasChangedPayload.java b/src/main/java/org/araymond/joal/web/messages/outgoing/impl/speed/SeedingSpeedHasChangedPayload.java index daa5489..5f0a5cf 100644 --- a/src/main/java/org/araymond/joal/web/messages/outgoing/impl/speed/SeedingSpeedHasChangedPayload.java +++ b/src/main/java/org/araymond/joal/web/messages/outgoing/impl/speed/SeedingSpeedHasChangedPayload.java @@ -24,6 +24,6 @@ public class SeedingSpeedHasChangedPayload implements MessagePayload { @RequiredArgsConstructor public static final class SpeedPayload { private final InfoHash infoHash; - private final Long bytesPerSecond; + private final long bytesPerSecond; } } diff --git a/src/main/java/org/araymond/joal/web/resources/WebSocketController.java b/src/main/java/org/araymond/joal/web/resources/WebSocketController.java index 1144dd4..5e00796 100644 --- a/src/main/java/org/araymond/joal/web/resources/WebSocketController.java +++ b/src/main/java/org/araymond/joal/web/resources/WebSocketController.java @@ -12,7 +12,6 @@ import org.araymond.joal.core.events.speed.SeedingSpeedsHasChangedEvent; import org.araymond.joal.core.events.torrent.files.TorrentFileAddedEvent; import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.torrent.torrent.MockedTorrent; -import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFacade; import org.araymond.joal.web.annotations.ConditionalOnWebUi; import org.araymond.joal.web.messages.incoming.config.Base64TorrentIncomingMessage; import org.araymond.joal.web.messages.incoming.config.ConfigIncomingMessage; @@ -92,7 +91,9 @@ public class WebSocketController { @MessageMapping("/torrents/delete") public void deleteTorrent(final String torrentInfoHash) { - this.seedManager.deleteTorrent(new InfoHash(torrentInfoHash.getBytes(MockedTorrent.BYTE_ENCODING))); + this.seedManager.deleteTorrent( + new InfoHash(torrentInfoHash.getBytes(MockedTorrent.BYTE_ENCODING)) + ); } /** @@ -108,15 +109,19 @@ public class WebSocketController { final LinkedList events = new LinkedList<>(); // client files list - events.add(StompMessage.wrap(new ListOfClientFilesPayload(new ListOfClientFilesEvent(this.seedManager.listClientFiles())))); + events.add(StompMessage.wrap( + new ListOfClientFilesPayload(new ListOfClientFilesEvent(this.seedManager.listClientFiles())) + )); // config - events.addFirst(StompMessage.wrap(new ConfigHasBeenLoadedPayload(new ConfigHasBeenLoadedEvent(this.seedManager.getCurrentConfig())))); + events.addFirst(StompMessage.wrap( + new ConfigHasBeenLoadedPayload(new ConfigHasBeenLoadedEvent(this.seedManager.getCurrentConfig())) + )); // torrent files list - for (final MockedTorrent torrent : this.seedManager.getTorrentFiles()) { - events.addFirst(StompMessage.wrap(new TorrentFileAddedPayload(new TorrentFileAddedEvent(torrent)))); - } + this.seedManager.getTorrentFiles().forEach(torrent -> events.addFirst( + StompMessage.wrap(new TorrentFileAddedPayload(new TorrentFileAddedEvent(torrent))) + )); // global state if (this.seedManager.isSeeding()) { @@ -131,10 +136,12 @@ public class WebSocketController { events.addFirst(StompMessage.wrap(new SeedingSpeedHasChangedPayload(new SeedingSpeedsHasChangedEvent(speedMap)))); } - // Announcers are the most likely to change due to a concurrent access, so we gather them as late as possible, and we put them at the top of the list. - for (final AnnouncerFacade announcerFacade : this.seedManager.getCurrentlySeedingAnnouncer()) { - events.addFirst(StompMessage.wrap(new SuccessfullyAnnouncePayload(new SuccessfullyAnnounceEvent(announcerFacade, RequestEvent.STARTED)))); - } + // Announcers are the most likely to change due to a concurrent access, + // so we gather them as late as possible, and we put them at the top of the list. + this.seedManager.getCurrentlySeedingAnnouncers().forEach(a -> events.addFirst( + StompMessage.wrap(new SuccessfullyAnnouncePayload(new SuccessfullyAnnounceEvent(a, RequestEvent.STARTED))) + )); + return events; } diff --git a/src/main/java/org/araymond/joal/web/services/corelistener/WebConfigEventListener.java b/src/main/java/org/araymond/joal/web/services/corelistener/WebConfigEventListener.java index f2162bb..bd485a5 100644 --- a/src/main/java/org/araymond/joal/web/services/corelistener/WebConfigEventListener.java +++ b/src/main/java/org/araymond/joal/web/services/corelistener/WebConfigEventListener.java @@ -32,7 +32,7 @@ public class WebConfigEventListener extends WebEventListener { @Order(Ordered.LOWEST_PRECEDENCE) @EventListener public void configHasBeenLoaded(final ConfigHasBeenLoadedEvent event) { - log.debug("Send ConfigHasBeenLoadedPayload to clients."); + log.debug("Send ConfigHasBeenLoadedPayload to clients"); this.messagingTemplate.convertAndSend("/config", new ConfigHasBeenLoadedPayload(event)); } @@ -40,7 +40,7 @@ public class WebConfigEventListener extends WebEventListener { @Order(Ordered.LOWEST_PRECEDENCE) @EventListener public void configIsInDirtyState(final ConfigurationIsInDirtyStateEvent event) { - log.debug("Send ConfigIsInDirtyStatePayload to clients."); + log.debug("Send ConfigIsInDirtyStatePayload to clients"); this.messagingTemplate.convertAndSend("/config", new ConfigIsInDirtyStatePayload(event)); } @@ -48,7 +48,7 @@ public class WebConfigEventListener extends WebEventListener { @Order(Ordered.LOWEST_PRECEDENCE) @EventListener public void clientFilesDiscovered(final ListOfClientFilesEvent event) { - log.debug("Send ListOfClientFilesPayload to clients."); + log.debug("Send ListOfClientFilesPayload to clients"); this.messagingTemplate.convertAndSend("/config", new ListOfClientFilesPayload(event)); } diff --git a/src/main/java/org/araymond/joal/web/services/corelistener/WebGlobalEventListener.java b/src/main/java/org/araymond/joal/web/services/corelistener/WebGlobalEventListener.java index 18b395c..79f1dee 100644 --- a/src/main/java/org/araymond/joal/web/services/corelistener/WebGlobalEventListener.java +++ b/src/main/java/org/araymond/joal/web/services/corelistener/WebGlobalEventListener.java @@ -1,6 +1,7 @@ package org.araymond.joal.web.services.corelistener; import lombok.extern.slf4j.Slf4j; +import org.araymond.joal.core.client.emulated.BitTorrentClientConfig; import org.araymond.joal.core.events.global.state.GlobalSeedStartedEvent; import org.araymond.joal.core.events.global.state.GlobalSeedStoppedEvent; import org.araymond.joal.web.annotations.ConditionalOnWebUi; @@ -13,8 +14,11 @@ import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; import javax.inject.Inject; + import java.util.Map; +import static org.springframework.http.HttpHeaders.USER_AGENT; + /** * Created by raymo on 22/06/2017. */ @@ -30,12 +34,12 @@ public class WebGlobalEventListener extends WebEventListener { @Order(Ordered.LOWEST_PRECEDENCE) @EventListener public void globalSeedStarted(final GlobalSeedStartedEvent event) { - log.debug("Send GlobalSeedStartedPayload to clients."); + log.debug("Send GlobalSeedStartedPayload to clients"); final String client = event.getBitTorrentClient().getHeaders().stream() - .filter(entry -> "User-Agent".equalsIgnoreCase(entry.getKey())) - .map(Map.Entry::getValue) + .filter(hdr -> USER_AGENT.equalsIgnoreCase(hdr.getKey())) .findFirst() + .map(Map.Entry::getValue) .orElse("Unknown"); this.messagingTemplate.convertAndSend("/global", new GlobalSeedStartedPayload(client)); @@ -44,7 +48,7 @@ public class WebGlobalEventListener extends WebEventListener { @Order(Ordered.LOWEST_PRECEDENCE) @EventListener public void globalSeedStopped(@SuppressWarnings("unused") final GlobalSeedStoppedEvent event) { - log.debug("Send GlobalSeedStoppedPayload to clients."); + log.debug("Send GlobalSeedStoppedPayload to clients"); this.messagingTemplate.convertAndSend("/global", new GlobalSeedStoppedPayload()); } diff --git a/src/test/java/org/araymond/joal/core/bandwith/weight/PeersAwareWeightCalculatorTest.java b/src/test/java/org/araymond/joal/core/bandwith/weight/PeersAwareWeightCalculatorTest.java index e006e6d..27fb505 100644 --- a/src/test/java/org/araymond/joal/core/bandwith/weight/PeersAwareWeightCalculatorTest.java +++ b/src/test/java/org/araymond/joal/core/bandwith/weight/PeersAwareWeightCalculatorTest.java @@ -1,10 +1,10 @@ package org.araymond.joal.core.bandwith.weight; import org.araymond.joal.core.bandwith.Peers; -import org.assertj.core.data.Offset; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.data.Offset.offset; public class PeersAwareWeightCalculatorTest { @@ -36,16 +36,16 @@ public class PeersAwareWeightCalculatorTest { final PeersAwareWeightCalculator calculator = new PeersAwareWeightCalculator(); assertThat(calculator.calculate(new Peers(1, 1))).isEqualTo(25); - assertThat(calculator.calculate(new Peers(2, 1))).isCloseTo(11.1, Offset.offset(0.1)); - assertThat(calculator.calculate(new Peers(30, 1))).isCloseTo(0.104058273, Offset.offset(0.00000001)); + assertThat(calculator.calculate(new Peers(2, 1))).isCloseTo(11.1, offset(0.1)); + assertThat(calculator.calculate(new Peers(30, 1))).isCloseTo(0.104058273, offset(0.00000001)); assertThat(calculator.calculate(new Peers(0, 1))).isEqualTo(0); assertThat(calculator.calculate(new Peers(1, 0))).isEqualTo(0); - assertThat(calculator.calculate(new Peers(2, 100))).isCloseTo(9611.687812, Offset.offset(0.0001)); + assertThat(calculator.calculate(new Peers(2, 100))).isCloseTo(9611.687812, offset(0.0001)); assertThat(calculator.calculate(new Peers(0, 100))).isEqualTo(0); - assertThat(calculator.calculate(new Peers(2000, 150))).isEqualTo(73.01243916, Offset.offset(0.00001)); - assertThat(calculator.calculate(new Peers(150, 2000))).isCloseTo(173066.5224, Offset.offset(0.01)); - assertThat(calculator.calculate(new Peers(80, 2000))).isCloseTo(184911.2426, Offset.offset(0.1)); - assertThat(calculator.calculate(new Peers(2000, 2000))).isEqualTo(50000); + assertThat(calculator.calculate(new Peers(2000, 150))).isEqualTo(73.01243916, offset(0.00001)); + assertThat(calculator.calculate(new Peers(150, 2000))).isCloseTo(173066.5224, offset(0.01)); + assertThat(calculator.calculate(new Peers(80, 2000))).isCloseTo(184911.2426, offset(0.1)); + assertThat(calculator.calculate(new Peers(2000, 2000))).isEqualTo(50_000); assertThat(calculator.calculate(new Peers(0, 0))).isEqualTo(0); } diff --git a/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfigSerializationTest.java b/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfigSerializationTest.java index 60ade5a..431db64 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfigSerializationTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfigSerializationTest.java @@ -11,7 +11,6 @@ import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; /** @@ -19,13 +18,14 @@ import static org.mockito.Mockito.mock; */ public class BitTorrentClientConfigSerializationTest { - private BitTorrentClientProvider provider = new BitTorrentClientProvider(mock(JoalConfigProvider.class), mock(ObjectMapper.class), mock(SeedManager.JoalFoldersPath.class)); + private BitTorrentClientProvider provider = new BitTorrentClientProvider(mock(JoalConfigProvider.class), + mock(ObjectMapper.class), mock(SeedManager.JoalFoldersPath.class)); private static final ObjectMapper mapper = new ObjectMapper(); private static final String validJSON = "{\"peerIdGenerator\":{\"refreshOn\":\"NEVER\",\"algorithm\": { \"type\": \"REGEX\", \"pattern\": \"-AZ5750-[a-zA-Z0-9]{12}\" }, \"shouldUrlEncode\": false},\"urlEncoder\":{\"encodingExclusionPattern\":\"[a-z]\",\"encodedHexCase\":\"lower\"},\"numwant\":200,\"numwantOnStop\":0,\"query\":\"info_hash={infohash}&peer_id={peerid}&supportcrypto=1&port={port}&azudp={port}&uploaded={uploaded}&downloaded={downloaded}&left={left}&corrupt=0&event={event}&numwant={numwant}&no_peer_id=1&compact=1&key={key}&azver=3\",\"keyGenerator\":{\"refreshOn\":\"NEVER\",\"algorithm\": { \"type\": \"HASH\", \"length\": 8 },\"keyCase\":\"lower\"},\"requestHeaders\":[{\"name\":\"User-Agent\",\"value\":\"Azureus 5.7.5.0;{os};1.8.0_66\"},{\"name\":\"Connection\",\"value\":\"close\"},{\"name\":\"Accept-Encoding\",\"value\":\"gzip\"},{\"name\":\"Accept\",\"value\":\"text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\"}]}"; @Test public void shouldFailToDeserializeIfPeerIdInfoIsNotDefined() throws IOException { - final String jsonWithoutPeerId = validJSON.replaceAll("\"peerIdGenerator\":[ ]*\\{.*?},", ""); + final String jsonWithoutPeerId = validJSON.replaceAll("\"peerIdGenerator\": *\\{.*?},", ""); assertThatThrownBy(() -> mapper.readValue(jsonWithoutPeerId, BitTorrentClientConfig.class)) .isInstanceOf(JsonMappingException.class) .hasMessageContaining("Missing required creator property 'peerIdGenerator'"); @@ -33,7 +33,7 @@ public class BitTorrentClientConfigSerializationTest { @Test public void shouldFailToDeserializeIfQueryIsNotDefined() throws IOException { - final String jsonWithoutQuery = validJSON.replaceAll("\"query\":[ ]*\".*?\",", ""); + final String jsonWithoutQuery = validJSON.replaceAll("\"query\": *\".*?\",", ""); assertThatThrownBy(() -> mapper.readValue(jsonWithoutQuery, BitTorrentClientConfig.class)) .isInstanceOf(JsonMappingException.class) .hasMessageContaining("Missing required creator property 'query'"); @@ -42,7 +42,7 @@ public class BitTorrentClientConfigSerializationTest { @Test public void shouldNotFailToDeserializeIfKeyGeneratorIsNotDefined() throws IOException { final String jsonWithoutKeyGenerator = validJSON - .replaceAll("\"keyGenerator\":[ ]*\\{.*?algorithm\":[ ]*\\{.*?}.*?},", "") + .replaceAll("\"keyGenerator\": *\\{.*?algorithm\": *\\{.*?}.*?},", "") .replaceAll("&key=\\{key}", ""); final BitTorrentClientConfig bitTorrentClientConfig = mapper.readValue(jsonWithoutKeyGenerator, BitTorrentClientConfig.class); @@ -52,7 +52,7 @@ public class BitTorrentClientConfigSerializationTest { @Test public void shouldFailToDeserializeIfUrlEncoderIsNotDefined() throws IOException { - final String jsonWithoutUrlEncoder = validJSON.replaceAll("\"urlEncoder\":[ ]*\\{.*?},", ""); + final String jsonWithoutUrlEncoder = validJSON.replaceAll("\"urlEncoder\": *\\{.*?},", ""); assertThatThrownBy(() -> mapper.readValue(jsonWithoutUrlEncoder, BitTorrentClientConfig.class)) .isInstanceOf(JsonMappingException.class) .hasMessageContaining("Missing required creator property 'urlEncoder'"); @@ -60,7 +60,7 @@ public class BitTorrentClientConfigSerializationTest { @Test public void shouldFailToDeserializeIfNumwantIsNotDefined() throws IOException { - final String jsonWithoutNumwant = validJSON.replaceAll("\"numwant\":[ ]*[0-9]+,", ""); + final String jsonWithoutNumwant = validJSON.replaceAll("\"numwant\": *\\d+,", ""); assertThatThrownBy(() -> mapper.readValue(jsonWithoutNumwant, BitTorrentClientConfig.class)) .isInstanceOf(JsonMappingException.class) .hasMessageContaining("Missing required creator property 'numwant'"); @@ -68,7 +68,7 @@ public class BitTorrentClientConfigSerializationTest { @Test public void shouldFailToDeserializeIfNumwantOnStopIsNotDefined() throws IOException { - final String jsonWithoutNumwantOnStop = validJSON.replaceAll("\"numwantOnStop\":[ ]*[0-9]+,", ""); + final String jsonWithoutNumwantOnStop = validJSON.replaceAll("\"numwantOnStop\": *\\d+,", ""); assertThatThrownBy(() -> mapper.readValue(jsonWithoutNumwantOnStop, BitTorrentClientConfig.class)) .isInstanceOf(JsonMappingException.class) .hasMessageContaining("Missing required creator property 'numwantOnStop'"); @@ -76,7 +76,7 @@ public class BitTorrentClientConfigSerializationTest { @Test public void shouldFailToDeserializeIfRequestHeadersIsNotDefined() throws IOException { - final String jsonWithoutHeaders = validJSON.replaceAll(",\"requestHeaders\":[ ]*\\[.*?\\]", ""); + final String jsonWithoutHeaders = validJSON.replaceAll(",\"requestHeaders\": *\\[.*?]", ""); assertThatThrownBy(() -> mapper.readValue(jsonWithoutHeaders, BitTorrentClientConfig.class)) .isInstanceOf(JsonMappingException.class) .hasMessageContaining("Missing required creator property 'requestHeaders'"); @@ -84,11 +84,11 @@ public class BitTorrentClientConfigSerializationTest { @Test public void shouldNotFailToDeserializeIfRequestHeadersIsEmpty() throws IOException { - final String jsonWithEmptyHeaders = validJSON.replaceAll("\"requestHeaders\":[ ]*\\[.*?\\]", "\"requestHeaders\":[]"); + final String jsonWithEmptyHeaders = validJSON.replaceAll("\"requestHeaders\": *\\[.*?]", "\"requestHeaders\":[]"); final BitTorrentClientConfig bitTorrentClientConfig = mapper.readValue(jsonWithEmptyHeaders, BitTorrentClientConfig.class); assertThat(bitTorrentClientConfig).isNotNull(); - assertThat(provider.createClient(bitTorrentClientConfig).getHeaders()).hasSize(0); + assertThat(provider.createClient(bitTorrentClientConfig).getHeaders()).isEmpty(); } @Test @@ -100,7 +100,8 @@ public class BitTorrentClientConfigSerializationTest { @Test public void shouldSerializeAndDeserialize() throws IOException { - assertThat(mapper.readTree(mapper.writeValueAsString(mapper.readValue(validJSON, BitTorrentClientConfig.class)))) + BitTorrentClientConfig deserConf = mapper.readValue(validJSON, BitTorrentClientConfig.class); + assertThat(mapper.readTree(mapper.writeValueAsString(deserConf))) .isEqualTo(mapper.readTree(validJSON)); } diff --git a/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfigTest.java b/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfigTest.java index 766cfb1..b676683 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfigTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientConfigTest.java @@ -12,17 +12,16 @@ import org.araymond.joal.core.client.emulated.utils.Casing; import org.araymond.joal.core.config.JoalConfigProvider; import org.araymond.joal.core.torrent.torrent.InfoHash; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import java.util.AbstractMap; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; +import java.util.*; +import java.util.AbstractMap.SimpleImmutableEntry; +import static com.google.common.base.StandardSystemProperty.OS_NAME; +import static java.lang.String.format; +import static java.lang.System.getProperty; import static org.araymond.joal.core.client.emulated.BitTorrentClientConfig.HttpHeader; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; /** @@ -60,7 +59,7 @@ public class BitTorrentClientConfigTest { assertThatThrownBy(() -> new BitTorrentClientConfig(defaultPeerIdGenerator, query, null, defaultUrlEncoder, requestHeaders, 200, 0)) .isInstanceOf(TorrentClientConfigIntegrityException.class) - .hasMessageContaining("Query string contains {key}, but no keyGenerator was found in .client file."); + .hasMessageContaining("Query string contains {key}, but no keyGenerator was found in .client file"); } @Test @@ -110,9 +109,11 @@ public class BitTorrentClientConfigTest { final BitTorrentClientConfig config = new BitTorrentClientConfig(peerIdGenerator, query, keyGenerator, defaultUrlEncoder, requestHeaders, 200, 0); final BitTorrentClient client = provider.createClient(config); - assertThat(client.getHeaders()).isEqualTo(requestHeaders.stream() - .map(header -> new AbstractMap.SimpleEntry<>(header.getName(), header.getValue())) - .collect(Collectors.toList()) + assertThat(client.getHeaders()).containsExactlyInAnyOrder( + new SimpleImmutableEntry<>("User-Agent", format("Azureus 5.7.5.0;%s;1.8.0_66", getProperty(OS_NAME.key()))), + new SimpleImmutableEntry<>("Connection", "close"), + new SimpleImmutableEntry<>("Accept-Encoding", "gzip"), + new SimpleImmutableEntry<>("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2") ); assertThat(client.getQuery()).isEqualTo(query); //noinspection OptionalGetWithoutIsPresent diff --git a/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientTest.java b/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientTest.java index f93163a..c69d603 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/BitTorrentClientTest.java @@ -28,7 +28,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import static com.google.common.base.StandardSystemProperty.JAVA_VERSION; +import static com.google.common.base.StandardSystemProperty.OS_NAME; import static java.lang.System.getProperty; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -48,18 +51,19 @@ public class BitTorrentClientTest { @SuppressWarnings("ConstantConditions") @Test - public void shouldCreateHeadersInSameOrderAndReplacePlaceHolders() { - final List headers = new ArrayList<>(); - headers.add(new HttpHeader("java-version", "{java}")); - headers.add(new HttpHeader("os", "{os}")); - headers.add(new HttpHeader("Connection", "close")); - headers.add(new HttpHeader("locale", "{locale}")); + public void shouldCreateHeadersAndReplacePlaceHolders() { + List headers = List.of( + new HttpHeader("java-version", "{java}"), + new HttpHeader("os", "{os}"), + new HttpHeader("Connection", "close"), + new HttpHeader("locale", "{locale}") + ); + final BitTorrentClient client = new BitTorrentClient(defaultPeerIdGenerator, defaultKeyGenerator, + defaultUrlEncoder, "myqueryString", headers, defaultNumwantProvider); - final BitTorrentClient client = new BitTorrentClient(defaultPeerIdGenerator, defaultKeyGenerator, defaultUrlEncoder, "myqueryString", headers, defaultNumwantProvider); - - assertThat(client.createRequestHeaders()).containsExactly( - new AbstractMap.SimpleEntry<>("java-version", getProperty("java.version")), - new AbstractMap.SimpleEntry<>("os", getProperty("os.name")), + assertThat(client.getHeaders()).containsExactlyInAnyOrder( + new AbstractMap.SimpleEntry<>("java-version", getProperty(JAVA_VERSION.key())), + new AbstractMap.SimpleEntry<>("os", getProperty(OS_NAME.key())), new AbstractMap.SimpleEntry<>("Connection", "close"), new AbstractMap.SimpleEntry<>("locale", Locale.getDefault().toLanguageTag()) ); @@ -76,13 +80,13 @@ public class BitTorrentClientTest { null, defaultUrlEncoder, "key={key}&event={event}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); assertThatThrownBy(() -> client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, connectionHandler)) .isInstanceOf(IllegalStateException.class) - .hasMessage("Client request query contains 'key' but BitTorrentClient does not have a key."); + .hasMessage("Client request query contains 'key' but BitTorrentClient does not have a key"); } @Test @@ -96,16 +100,16 @@ public class BitTorrentClientTest { defaultKeyGenerator, defaultUrlEncoder, "numwant={numwant}", - Collections.emptyList(), + emptyList(), numwantProvider ); assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, connectionHandler)) - .isEqualToIgnoringCase("numwant=10"); + .isEqualTo("numwant=10"); assertThat(client.createRequestQuery(RequestEvent.NONE, new InfoHash("a".getBytes()), stats, connectionHandler)) - .isEqualToIgnoringCase("numwant=10"); + .isEqualTo("numwant=10"); assertThat(client.createRequestQuery(RequestEvent.STOPPED, new InfoHash("a".getBytes()), stats, connectionHandler)) - .isEqualToIgnoringCase("numwant=50"); + .isEqualTo("numwant=50"); } @Test @@ -117,18 +121,36 @@ public class BitTorrentClientTest { defaultKeyGenerator, defaultUrlEncoder, "ipv4={ip}&ipv6={ipv6}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); // If Ipv4, should fill param and remove {ipv6} final ConnectionHandler mockedIpv4 = ConnectionHandlerTest.createMockedIpv4(12); assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, mockedIpv4)) - .isEqualToIgnoringCase("ipv4=" + mockedIpv4.getIpAddress().getHostAddress()); + .isEqualTo("ipv4=" + mockedIpv4.getIpAddress().getHostAddress()); // If Ipv6, should fill param and remove {ip} final ConnectionHandler mockedIpv6 = ConnectionHandlerTest.createMockedIpv6(12); assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, mockedIpv6)) - .isEqualToIgnoringCase("ipv6=" + mockedIpv6.getIpAddress().getHostAddress()); + .isEqualTo("ipv6=" + mockedIpv6.getIpAddress().getHostAddress()); + } + + @Test + public void shouldCollapseMultipleAmpersandCharacters() { + final ConnectionHandler connectionHandler = ConnectionHandlerTest.createMockedIpv4(12345); + final TorrentSeedStats stats = TorrentSeedStatsTest.createOne(); + + final BitTorrentClient client = new BitTorrentClient( + defaultPeerIdGenerator, + defaultKeyGenerator, + defaultUrlEncoder, + "&&uploaded={uploaded}&&&port={port}&&&&left={left}&&&&&", + emptyList(), + defaultNumwantProvider + ); + + assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, connectionHandler)) + .isEqualTo("uploaded=0&port=12345&left=0"); } @Test @@ -141,12 +163,12 @@ public class BitTorrentClientTest { defaultKeyGenerator, defaultUrlEncoder, "port={port}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, connectionHandler)) - .isEqualToIgnoringCase("port=12345"); + .isEqualTo("port=12345"); } @@ -164,12 +186,12 @@ public class BitTorrentClientTest { defaultKeyGenerator, defaultUrlEncoder, "uploaded={uploaded}&downloaded={downloaded}&left={left}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, connectionHandler)) - .isEqualToIgnoringCase("uploaded=123456&downloaded=1478&left=369"); + .isEqualTo("uploaded=123456&downloaded=1478&left=369"); } @Test @@ -182,7 +204,7 @@ public class BitTorrentClientTest { defaultKeyGenerator, defaultUrlEncoder, "nop={wtfIsThisPlaceHolder}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); @@ -200,12 +222,12 @@ public class BitTorrentClientTest { defaultKeyGenerator, new UrlEncoder("", Casing.UPPER), "infohash={infohash}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, connectionHandler)) - .isEqualToIgnoringCase("infohash=%61"); + .isEqualTo("infohash=%61"); } @Test @@ -218,12 +240,12 @@ public class BitTorrentClientTest { defaultKeyGenerator, new UrlEncoder("", Casing.UPPER), "peerid={peerid}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, connectionHandler)) - .isEqualToIgnoringCase("peerid=%2D%41%41%2D%61%61%61%61%61%61%61%61%61%61%61%61%61%61%61%61"); + .isEqualTo("peerid=%2D%41%41%2D%61%61%61%61%61%61%61%61%61%61%61%61%61%61%61%61"); } @Test @@ -236,12 +258,12 @@ public class BitTorrentClientTest { defaultKeyGenerator, new UrlEncoder("", Casing.UPPER), "peerid={peerid}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, connectionHandler)) - .isEqualToIgnoringCase("peerid=-AA-aaaaaaaaaaaaaaaa"); + .isEqualTo("peerid=-AA-aaaaaaaaaaaaaaaa"); } @Test @@ -254,17 +276,19 @@ public class BitTorrentClientTest { defaultKeyGenerator, defaultUrlEncoder, "event={event}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, connectionHandler)) .isEqualToIgnoringCase("event=STARTED"); - // If none, event must be deleted - assertThat(client.createRequestQuery(RequestEvent.NONE, new InfoHash("a".getBytes()), stats, connectionHandler)) - .isEqualToIgnoringCase(""); assertThat(client.createRequestQuery(RequestEvent.STOPPED, new InfoHash("a".getBytes()), stats, connectionHandler)) .isEqualToIgnoringCase("event=STOPPED"); + // If none, event must be deleted: + assertThat(client.createRequestQuery(RequestEvent.NONE, new InfoHash("a".getBytes()), stats, connectionHandler)) + .isEmpty(); + assertThat(client.createRequestQuery(null, new InfoHash("a".getBytes()), stats, connectionHandler)) + .isEmpty(); } @Test @@ -279,12 +303,12 @@ public class BitTorrentClientTest { keyGenerator, urlEncoder, "key={key}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); assertThat(client.createRequestQuery(RequestEvent.STARTED, new InfoHash("a".getBytes()), stats, connectionHandler)) - .isEqualToIgnoringCase("key=" + urlEncoder.encode(keyGenerator.getKey(new InfoHash("a".getBytes()), RequestEvent.STARTED))); + .isEqualTo("key=" + urlEncoder.encode(keyGenerator.getKey(new InfoHash("a".getBytes()), RequestEvent.STARTED))); } @Test @@ -297,7 +321,7 @@ public class BitTorrentClientTest { defaultKeyGenerator, defaultUrlEncoder, "uploaded={uploaded}&event={event}&uploaded={uploaded}", - Collections.emptyList(), + emptyList(), defaultNumwantProvider ); @@ -306,13 +330,9 @@ public class BitTorrentClientTest { } @Test - public void shouldFailsIfHeadersContainsRemainingPlaceHolder() { - final List headers = new ArrayList<>(); - headers.add(new HttpHeader("qmqm", "{aohdksdf}")); - - final BitTorrentClient client = new BitTorrentClient(defaultPeerIdGenerator, defaultKeyGenerator, defaultUrlEncoder, "myqueryString", headers, defaultNumwantProvider); - - assertThatThrownBy(client::createRequestHeaders) + public void shouldFailIfHeadersContainRemainingPlaceHolder() { + final List headers = List.of(new HttpHeader("qmqm", "{aohdksdf}")); + assertThatThrownBy(() -> new BitTorrentClient(defaultPeerIdGenerator, defaultKeyGenerator, defaultUrlEncoder, "myqueryString", headers, defaultNumwantProvider)) .isInstanceOf(UnrecognizedClientPlaceholder.class); } @@ -367,7 +387,7 @@ public class BitTorrentClientTest { @Test public void shouldBuildIfHeadersIsEmpty() { - final BitTorrentClient client = new BitTorrentClient(defaultPeerIdGenerator, defaultKeyGenerator, defaultUrlEncoder, "myqueryString", Collections.emptyList(), defaultNumwantProvider); + final BitTorrentClient client = new BitTorrentClient(defaultPeerIdGenerator, defaultKeyGenerator, defaultUrlEncoder, "myqueryString", emptyList(), defaultNumwantProvider); assertThat(client.getHeaders()).isEmpty(); } @@ -412,7 +432,7 @@ public class BitTorrentClientTest { final TorrentSeedStats stats = TorrentSeedStatsTest.createOne(); client.createRequestQuery(RequestEvent.STARTED, new InfoHash("adb".getBytes()), stats, connHandler); - client.createRequestHeaders(); + // headers are generated in constructor } catch (final Exception e) { fail("Exception for client file [" + file.getName() + "]", e); } @@ -429,7 +449,6 @@ public class BitTorrentClientTest { final BitTorrentClientConfig clientConfig = mapper.readValue(json, BitTorrentClientConfig.class); final BitTorrentClient client = provider.createClient(clientConfig); - final String query = client.getQuery(); assertThat(query) .contains("info_hash={infohash}") diff --git a/src/test/java/org/araymond/joal/core/client/emulated/HttpHeaderSerializationTest.java b/src/test/java/org/araymond/joal/core/client/emulated/HttpHeaderSerializationTest.java index 7ead1b1..748c177 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/HttpHeaderSerializationTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/HttpHeaderSerializationTest.java @@ -50,7 +50,8 @@ public class HttpHeaderSerializationTest { @Test public void shouldSerializeAndDeserialize() throws IOException { final HttpHeader header = new HttpHeader("Connection", "close"); - assertThat(mapper.readValue(mapper.writeValueAsString(header), HttpHeader.class)).usingRecursiveComparison().isEqualTo(header); + final HttpHeader deserHdr = mapper.readValue(mapper.writeValueAsString(header), HttpHeader.class); + assertThat(deserHdr).isEqualTo(header); } } diff --git a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/KeyGeneratorTest.java b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/KeyGeneratorTest.java index 0c3dd74..a90de84 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/KeyGeneratorTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/KeyGeneratorTest.java @@ -24,7 +24,7 @@ public class KeyGeneratorTest { public void shouldNotBuildWithNullAlgorithm() { assertThatThrownBy(() -> new DefaultKeyGenerator(null, Casing.NONE)) .isInstanceOf(TorrentClientConfigIntegrityException.class) - .hasMessage("key algorithm must not be null."); + .hasMessage("key algorithm must not be null"); } diff --git a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/NeverRefreshKeyGeneratorTest.java b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/NeverRefreshKeyGeneratorTest.java index a29f868..fa370bf 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/NeverRefreshKeyGeneratorTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/NeverRefreshKeyGeneratorTest.java @@ -9,6 +9,8 @@ import org.mockito.Mockito; import java.nio.ByteBuffer; +import static org.mockito.Mockito.times; + /** * Created by raymo on 16/07/2017. */ @@ -26,7 +28,7 @@ public class NeverRefreshKeyGeneratorTest { generator.getKey(infoHash, RequestEvent.STARTED); } - Mockito.verify(algo, Mockito.times(1)).generate(); + Mockito.verify(algo, times(1)).generate(); } } diff --git a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/TimedOrAfterStartedAnnounceRefreshKeyGeneratorTest.java b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/TimedOrAfterStartedAnnounceRefreshKeyGeneratorTest.java index e0caa50..d26aa86 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/TimedOrAfterStartedAnnounceRefreshKeyGeneratorTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/TimedOrAfterStartedAnnounceRefreshKeyGeneratorTest.java @@ -19,22 +19,13 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; */ public class TimedOrAfterStartedAnnounceRefreshKeyGeneratorTest { - @Test - public void shouldNotBuildWithoutRefreshEvery() { - final KeyAlgorithm algo = Mockito.mock(KeyAlgorithm.class); - Mockito.when(algo.generate()).thenReturn("do-not-care"); - assertThatThrownBy(() -> new TimedOrAfterStartedAnnounceRefreshKeyGenerator(null, algo, Casing.NONE)) - .isInstanceOf(TorrentClientConfigIntegrityException.class) - .hasMessage("refreshEvery must be greater than 0."); - } - @Test public void shouldNotBuildWithRefreshEveryLessThanOne() { final KeyAlgorithm algo = Mockito.mock(KeyAlgorithm.class); Mockito.when(algo.generate()).thenReturn("do-not-care"); assertThatThrownBy(() -> new TimedOrAfterStartedAnnounceRefreshKeyGenerator(0, algo, Casing.NONE)) .isInstanceOf(TorrentClientConfigIntegrityException.class) - .hasMessage("refreshEvery must be greater than 0."); + .hasMessage("refreshEvery must be greater than 0"); } @Test diff --git a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/TimedRefreshKeyGeneratorTest.java b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/TimedRefreshKeyGeneratorTest.java index 05ca0dc..4a0468d 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/TimedRefreshKeyGeneratorTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/TimedRefreshKeyGeneratorTest.java @@ -19,22 +19,13 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; */ public class TimedRefreshKeyGeneratorTest { - @Test - public void shouldNotBuildWithoutRefreshEvery() { - final KeyAlgorithm algo = Mockito.mock(KeyAlgorithm.class); - Mockito.when(algo.generate()).thenReturn("do-not-care"); - assertThatThrownBy(() -> new TimedRefreshKeyGenerator(null, algo, Casing.NONE)) - .isInstanceOf(TorrentClientConfigIntegrityException.class) - .hasMessage("refreshEvery must be greater than 0."); - } - @Test public void shouldNotBuildWithRefreshEveryLessThanOne() { final KeyAlgorithm algo = Mockito.mock(KeyAlgorithm.class); Mockito.when(algo.generate()).thenReturn("do-not-care"); assertThatThrownBy(() -> new TimedRefreshKeyGenerator(0, algo, Casing.NONE)) .isInstanceOf(TorrentClientConfigIntegrityException.class) - .hasMessage("refreshEvery must be greater than 0."); + .hasMessage("refreshEvery must be greater than 0"); } @Test diff --git a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/DigitRangeTransformedToHexWithoutLeadingZeroAlgorithmTest.java b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/DigitRangeTransformedToHexWithoutLeadingZeroAlgorithmTest.java index 54dd17d..fe6120d 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/DigitRangeTransformedToHexWithoutLeadingZeroAlgorithmTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/DigitRangeTransformedToHexWithoutLeadingZeroAlgorithmTest.java @@ -12,18 +12,6 @@ import static org.mockito.Mockito.doReturn; public class DigitRangeTransformedToHexWithoutLeadingZeroAlgorithmTest { - @Test - public void shouldNotBuildWithNullLowerBound() { - assertThatThrownBy(() -> new DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm(null, 10_000L)) - .isInstanceOf(TorrentClientConfigIntegrityException.class); - } - @Test - - public void shouldNotBuildWithNullUppererBound() { - assertThatThrownBy(() -> new DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm(0L, null)) - .isInstanceOf(TorrentClientConfigIntegrityException.class); - } - @Test public void shouldNotBuildWithLowerBoundGreaterThanUpperBound() { assertThatThrownBy(() -> new DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm(100L, 1L)) diff --git a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashKeyAlgorithmTest.java b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashKeyAlgorithmTest.java index bbddf96..6d47b4c 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashKeyAlgorithmTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashKeyAlgorithmTest.java @@ -1,19 +1,11 @@ package org.araymond.joal.core.client.emulated.generator.key.algorithm; -import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; public class HashKeyAlgorithmTest { - @Test - public void shouldNotBuildWithNullLength() { - assertThatThrownBy(() -> new HashKeyAlgorithm(null)) - .isInstanceOf(TorrentClientConfigIntegrityException.class); - } - @Test public void shouldGenerateValidHash() { final HashKeyAlgorithm algo = new HashKeyAlgorithm(8); diff --git a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashNoLeadingZeroKeyAlgorithmTest.java b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashNoLeadingZeroKeyAlgorithmTest.java index 05880e8..a3961d1 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashNoLeadingZeroKeyAlgorithmTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/generator/key/algorithm/HashNoLeadingZeroKeyAlgorithmTest.java @@ -1,21 +1,13 @@ package org.araymond.joal.core.client.emulated.generator.key.algorithm; -import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; public class HashNoLeadingZeroKeyAlgorithmTest { - @Test - public void shouldNotBuildWithNullLength() { - assertThatThrownBy(() -> new HashNoLeadingZeroKeyAlgorithm(null)) - .isInstanceOf(TorrentClientConfigIntegrityException.class); - } - @Test public void shouldGenerateValidHash() { final HashNoLeadingZeroKeyAlgorithm algo = new HashNoLeadingZeroKeyAlgorithm(8); diff --git a/src/test/java/org/araymond/joal/core/client/emulated/generator/numwant/NumwantProviderTest.java b/src/test/java/org/araymond/joal/core/client/emulated/generator/numwant/NumwantProviderTest.java index 716b1d7..4834cde 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/generator/numwant/NumwantProviderTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/generator/numwant/NumwantProviderTest.java @@ -11,18 +11,11 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; */ public class NumwantProviderTest { - @Test - public void shouldNotBuildWithoutNumwant() { - assertThatThrownBy(() -> new NumwantProvider(null, 0)) - .isInstanceOf(NullPointerException.class) - .hasMessage("numwant must not be null."); - } - @Test public void shouldNotBuildWithNumwantLessThanOne() { assertThatThrownBy(() -> new NumwantProvider(0, 0)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("numwant must be at least 1."); + .hasMessage("numwant must be at least 1"); } @Test @@ -32,18 +25,11 @@ public class NumwantProviderTest { assertThat(numwantProvider.get(RequestEvent.STARTED)).isEqualTo(1); } - @Test - public void shouldNotBuildWithoutNumwantOnStop() { - assertThatThrownBy(() -> new NumwantProvider(200, null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("numwantOnStop must not be null."); - } - @Test public void shouldNotBuildWithNumwantOnStopLessThanZero() { assertThatThrownBy(() -> new NumwantProvider(200, -1)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("numwantOnStop must be at least 0."); + .hasMessage("numwantOnStop must be at least 0"); } @Test diff --git a/src/test/java/org/araymond/joal/core/client/emulated/generator/peerid/PeerIdGeneratorTest.java b/src/test/java/org/araymond/joal/core/client/emulated/generator/peerid/PeerIdGeneratorTest.java index f95a3b3..ea1711c 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/generator/peerid/PeerIdGeneratorTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/generator/peerid/PeerIdGeneratorTest.java @@ -30,7 +30,7 @@ public class PeerIdGeneratorTest { } }) .isInstanceOf(TorrentClientConfigIntegrityException.class) - .hasMessage("peerId algorithm must not be null."); + .hasMessage("peerId algorithm must not be null"); } @Test @@ -87,7 +87,7 @@ public class PeerIdGeneratorTest { final PeerIdGenerator peerIdGenerator2 = new DefaultPeerIdGenerator("-my\\.pre-[a-zA-Z]{12}", false); assertThat(peerIdGenerator.hashCode()).isEqualTo(peerIdGenerator2.hashCode()); } - + private static class DefaultPeerIdGenerator extends PeerIdGenerator { protected DefaultPeerIdGenerator(final String pattern, final boolean isUrlEncoded) { diff --git a/src/test/java/org/araymond/joal/core/client/emulated/generator/peerid/TimedRefreshPeerIdGeneratorTest.java b/src/test/java/org/araymond/joal/core/client/emulated/generator/peerid/TimedRefreshPeerIdGeneratorTest.java index 66066d0..35123b6 100644 --- a/src/test/java/org/araymond/joal/core/client/emulated/generator/peerid/TimedRefreshPeerIdGeneratorTest.java +++ b/src/test/java/org/araymond/joal/core/client/emulated/generator/peerid/TimedRefreshPeerIdGeneratorTest.java @@ -12,24 +12,18 @@ import java.time.temporal.ChronoUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.times; /** * Created by raymo on 16/07/2017. */ public class TimedRefreshPeerIdGeneratorTest { - @Test - public void shouldNotBuildWithoutRefreshEvery() { - assertThatThrownBy(() -> new TimedRefreshPeerIdGenerator(null, Mockito.mock(PeerIdAlgorithm.class), false)) - .isInstanceOf(TorrentClientConfigIntegrityException.class) - .hasMessage("refreshEvery must be greater than 0."); - } - @Test public void shouldNotBuildWithRefreshEveryLessThanOne() { assertThatThrownBy(() -> new TimedRefreshPeerIdGenerator(0, Mockito.mock(PeerIdAlgorithm.class), false)) .isInstanceOf(TorrentClientConfigIntegrityException.class) - .hasMessage("refreshEvery must be greater than 0."); + .hasMessage("refreshEvery must be greater than 0"); } @Test @@ -46,15 +40,15 @@ public class TimedRefreshPeerIdGeneratorTest { final TimedRefreshPeerIdGenerator generator = new TimedRefreshPeerIdGenerator(1, algo, false); final InfoHash infoHash = new InfoHash(new byte[] { 22 }); - for( int i = 0; i < 10; ++i) { + for (int i = 0; i < 10; ++i) { generator.getPeerId(infoHash, RequestEvent.STARTED); } - Mockito.verify(algo, Mockito.times(1)).generate(); + Mockito.verify(algo, times(1)).generate(); generator.lastGeneration = LocalDateTime.now().minus(10, ChronoUnit.SECONDS); generator.getPeerId(infoHash, RequestEvent.STARTED); - Mockito.verify(algo, Mockito.times(2)).generate(); + Mockito.verify(algo, times(2)).generate(); } } diff --git a/src/test/java/org/araymond/joal/core/config/AppConfigurationTest.java b/src/test/java/org/araymond/joal/core/config/AppConfigurationTest.java index 9a5c6c5..b638a7e 100644 --- a/src/test/java/org/araymond/joal/core/config/AppConfigurationTest.java +++ b/src/test/java/org/araymond/joal/core/config/AppConfigurationTest.java @@ -18,7 +18,7 @@ public class AppConfigurationTest { public void shouldNotBuildIfMinUploadRateIsLessThanZero() { assertThatThrownBy(() -> new AppConfiguration(-1L, 190L, 2, "azureus.client", false)) .isInstanceOf(AppConfigurationIntegrityException.class) - .hasMessageContaining("minUploadRate must be at least 0."); + .hasMessageContaining("minUploadRate must be at least 0"); } @Test @@ -39,7 +39,7 @@ public class AppConfigurationTest { public void shouldNotBuildIfMaxUploadRateIsLessThanZero() { assertThatThrownBy(() -> new AppConfiguration(180L, -1L, 2, "azureus.client", false)) .isInstanceOf(AppConfigurationIntegrityException.class) - .hasMessageContaining("maxUploadRate must greater or equal to 0."); + .hasMessageContaining("maxUploadRate must greater or equal to 0"); } @Test @@ -54,7 +54,7 @@ public class AppConfigurationTest { public void shouldNotBuildIfMaxRateIsLesserThanMinRate() { assertThatThrownBy(() -> new AppConfiguration(180L, 179L, 2, "azureus.client", false)) .isInstanceOf(AppConfigurationIntegrityException.class) - .hasMessageContaining("maxUploadRate must be greater or equal to minUploadRate."); + .hasMessageContaining("maxUploadRate must be greater or equal to minUploadRate"); } @Test @@ -69,7 +69,7 @@ public class AppConfigurationTest { public void shouldNotBuildIfSimultaneousSeedIsLessThanOne() { assertThatThrownBy(() -> new AppConfiguration(180L, 190L, 0, "azureus.client", false)) .isInstanceOf(AppConfigurationIntegrityException.class) - .hasMessageContaining("simultaneousSeed must be greater than 0."); + .hasMessageContaining("simultaneousSeed must be greater than 0"); } @Test @@ -83,14 +83,14 @@ public class AppConfigurationTest { public void shouldNotBuildIfClientIsNull() { assertThatThrownBy(() -> new AppConfiguration(180L, 190L, 2, null, false)) .isInstanceOf(AppConfigurationIntegrityException.class) - .hasMessageContaining("client is required, no file name given."); + .hasMessageContaining("client is required, no file name given"); } @Test public void shouldNotBuildIfClientIsEmpty() { assertThatThrownBy(() -> new AppConfiguration(180L, 190L, 2, " ", false)) .isInstanceOf(AppConfigurationIntegrityException.class) - .hasMessageContaining("client is required, no file name given."); + .hasMessageContaining("client is required, no file name given"); } @Test diff --git a/src/test/java/org/araymond/joal/core/config/JoalConfigProviderTest.java b/src/test/java/org/araymond/joal/core/config/JoalConfigProviderTest.java index efc88c8..6e611d5 100644 --- a/src/test/java/org/araymond/joal/core/config/JoalConfigProviderTest.java +++ b/src/test/java/org/araymond/joal/core/config/JoalConfigProviderTest.java @@ -15,7 +15,6 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.Comparator; import java.util.Random; import static org.assertj.core.api.Assertions.assertThat; @@ -41,7 +40,7 @@ public class JoalConfigProviderTest { final SeedManager.JoalFoldersPath fakeFolders = new SeedManager.JoalFoldersPath(Paths.get("nop")); assertThatThrownBy(() -> new JoalConfigProvider(new ObjectMapper(), fakeFolders, Mockito.mock(ApplicationEventPublisher.class))) .isInstanceOf(FileNotFoundException.class) - .hasMessageContaining("App configuration file [" + fakeFolders.getConfPath() + File.separator + "config.json] not found"); + .hasMessageContaining("App configuration file [" + fakeFolders.getConfDirRootPath() + File.separator + "config.json] not found"); } @Test @@ -63,22 +62,25 @@ public class JoalConfigProviderTest { @Test public void shouldGetConf() throws FileNotFoundException { final JoalConfigProvider provider = new JoalConfigProvider(new ObjectMapper(), joalFoldersPath, Mockito.mock(ApplicationEventPublisher.class)); - provider.init(); + AppConfiguration conf = provider.init(); + AppConfiguration confGet = provider.get(); - assertThat(provider.get()).isEqualTo(defaultConfig); + assertThat(confGet).isEqualTo(defaultConfig); + assertThat(confGet).isEqualTo(conf); } @Test - public void shouldAlwaysReturnsSameConf() throws FileNotFoundException { + public void shouldAlwaysReturnSameConf() throws FileNotFoundException { final JoalConfigProvider provider = new JoalConfigProvider(new ObjectMapper(), joalFoldersPath, Mockito.mock(ApplicationEventPublisher.class)); - provider.init(); + AppConfiguration confInit = provider.init(); assertThat(provider.get()).usingComparator((o1, o2) -> { if (o1 == o2) return 0; return -1; }) .isEqualTo(provider.get()) - .isEqualTo(provider.get()); + .isEqualTo(provider.get()) + .isEqualTo(confInit); } @Test @@ -97,7 +99,7 @@ public class JoalConfigProviderTest { @Test public void shouldWriteConfigurationFile() throws IOException { - new ObjectMapper().writeValue(rewritableJoalFoldersPath.getConfPath().resolve("config.json").toFile(), defaultConfig); + new ObjectMapper().writeValue(rewritableJoalFoldersPath.getConfDirRootPath().resolve("config.json").toFile(), defaultConfig); try { final JoalConfigProvider provider = new JoalConfigProvider(new ObjectMapper(), rewritableJoalFoldersPath, Mockito.mock(ApplicationEventPublisher.class)); final Random rand = new Random(); @@ -113,7 +115,7 @@ public class JoalConfigProviderTest { assertThat(provider.loadConfiguration()).isEqualTo(newConf); } finally { - Files.deleteIfExists(rewritableJoalFoldersPath.getConfPath().resolve("config.json")); + Files.deleteIfExists(rewritableJoalFoldersPath.getConfDirRootPath().resolve("config.json")); } } diff --git a/src/test/java/org/araymond/joal/core/torrent/torrent/InfoHashTest.java b/src/test/java/org/araymond/joal/core/torrent/torrent/InfoHashTest.java index edca267..633d500 100644 --- a/src/test/java/org/araymond/joal/core/torrent/torrent/InfoHashTest.java +++ b/src/test/java/org/araymond/joal/core/torrent/torrent/InfoHashTest.java @@ -34,7 +34,7 @@ public class InfoHashTest { @Test public void shouldRemoveNonHumanReadableChars() { - final InfoHash infoHash = new InfoHash("a\u0001b\u0001\u0001cc".getBytes()); + final InfoHash infoHash = new InfoHash("a\u0001b\u0001\u0001cc\u0002".getBytes()); assertThat(infoHash.getHumanReadable()).isEqualTo("abcc"); } diff --git a/src/test/java/org/araymond/joal/core/torrent/torrent/MockedTorrentTest.java b/src/test/java/org/araymond/joal/core/torrent/torrent/MockedTorrentTest.java index 5d5b2b6..62f35f3 100644 --- a/src/test/java/org/araymond/joal/core/torrent/torrent/MockedTorrentTest.java +++ b/src/test/java/org/araymond/joal/core/torrent/torrent/MockedTorrentTest.java @@ -1,6 +1,5 @@ package org.araymond.joal.core.torrent.torrent; -import com.google.common.collect.Lists; import com.turn.ttorrent.common.Torrent; import org.araymond.joal.core.utils.TorrentFileCreator; import org.junit.jupiter.api.Test; @@ -26,9 +25,11 @@ public class MockedTorrentTest { doReturn(new InfoHash(infoHash.getBytes())).when(torrent).getTorrentInfoHash(); doReturn(infoHash.getBytes()).when(torrent).getInfoHash(); - final List> uris = Lists.newArrayList(); - uris.add(Lists.newArrayList(URI.create("http://localhost"), URI.create("https://localhost"))); - uris.add(Lists.newArrayList(URI.create("http://127.0.0.1"), URI.create("https://127.0.0.1"))); + final List> uris = List.of( + List.of(URI.create("http://localhost"), URI.create("https://localhost")), + List.of(URI.create("http://127.0.0.1"), URI.create("https://127.0.0.1")) + ); + doReturn(uris).when(torrent).getAnnounceList(); doReturn(Torrent.byteArrayToHexString(infoHash.getBytes())).when(torrent).getHexInfoHash(); doReturn("generic torrent").when(torrent).getName(); diff --git a/src/test/java/org/araymond/joal/core/torrent/watcher/TorrentFileProviderTest.java b/src/test/java/org/araymond/joal/core/torrent/watcher/TorrentFileProviderTest.java index 1e668d2..bafdba8 100644 --- a/src/test/java/org/araymond/joal/core/torrent/watcher/TorrentFileProviderTest.java +++ b/src/test/java/org/araymond/joal/core/torrent/watcher/TorrentFileProviderTest.java @@ -31,13 +31,13 @@ public class TorrentFileProviderTest { private static final SeedManager.JoalFoldersPath joalFoldersPath = new SeedManager.JoalFoldersPath(Paths.get("src/test/resources/configtest")); private void resetDirectories() throws IOException { - if (Files.exists(joalFoldersPath.getTorrentFilesPath())) { - Files.walk(joalFoldersPath.getTorrentFilesPath(), FileVisitOption.FOLLOW_LINKS) + if (Files.exists(joalFoldersPath.getTorrentsDirPath())) { + Files.walk(joalFoldersPath.getTorrentsDirPath(), FileVisitOption.FOLLOW_LINKS) .sorted(Comparator.reverseOrder()) .map(Path::toFile) .forEach(File::delete); } - Files.createDirectory(joalFoldersPath.getTorrentFilesPath()); + Files.createDirectory(joalFoldersPath.getTorrentsDirPath()); } @AfterEach @@ -55,10 +55,10 @@ public class TorrentFileProviderTest { @Test public void shouldCreateArchiveFolderIfNotCreatedAlready() throws IOException { - Files.deleteIfExists(joalFoldersPath.getTorrentArchivedPath()); - assertThat(exists(joalFoldersPath.getTorrentArchivedPath())).isFalse(); + Files.deleteIfExists(joalFoldersPath.getTorrentArchiveDirPath()); + assertThat(exists(joalFoldersPath.getTorrentArchiveDirPath())).isFalse(); new TorrentFileProvider(joalFoldersPath).init(); - assertThat(exists(joalFoldersPath.getTorrentArchivedPath())).isTrue(); + assertThat(exists(joalFoldersPath.getTorrentArchiveDirPath())).isTrue(); } @Test @@ -67,103 +67,103 @@ public class TorrentFileProviderTest { assertThatThrownBy(() -> provider.getTorrentNotIn(new ArrayList<>())) .isInstanceOf(NoMoreTorrentsFileAvailableException.class) - .hasMessageContaining("No more torrent file available."); + .hasMessageContaining("No more torrent files available"); } @Test public void shouldAddFileToListOnCreation() throws IOException { - TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); assertThat(provider.getTorrentCount()).isEqualTo(0); - provider.onFileCreate(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent").toFile()); + provider.onFileCreate(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent").toFile()); assertThat(provider.getTorrentCount()).isEqualTo(1); } @Test public void shouldNotAddDuplicatedFiles() throws IOException { - TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); assertThat(provider.getTorrentCount()).isEqualTo(0); - provider.onFileCreate(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent").toFile()); - provider.onFileCreate(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent").toFile()); + provider.onFileCreate(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent").toFile()); + provider.onFileCreate(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent").toFile()); assertThat(provider.getTorrentCount()).isEqualTo(1); } @Test public void shouldRemoveFileFromListOnDeletion() throws IOException { final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); - TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ninja.torrent"), TorrentFileCreator.TorrentType.NINJA_HEAT); + TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ninja.torrent"), TorrentFileCreator.TorrentType.NINJA_HEAT); assertThat(provider.getTorrentCount()).isEqualTo(0); - provider.onFileCreate(joalFoldersPath.getTorrentFilesPath().resolve("ninja.torrent").toFile()); + provider.onFileCreate(joalFoldersPath.getTorrentsDirPath().resolve("ninja.torrent").toFile()); assertThat(provider.getTorrentCount()).isEqualTo(1); - provider.onFileDelete(joalFoldersPath.getTorrentFilesPath().resolve("ninja.torrent").toFile()); + provider.onFileDelete(joalFoldersPath.getTorrentsDirPath().resolve("ninja.torrent").toFile()); assertThat(provider.getTorrentCount()).isEqualTo(0); } @Test public void shouldRemoveThenAddFileToListOnUpdate() throws IOException { - TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); - provider.onFileCreate(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent").toFile()); + provider.onFileCreate(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent").toFile()); assertThat(provider.getTorrentCount()).isEqualTo(1); - provider.onFileChange(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent").toFile()); + provider.onFileChange(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent").toFile()); assertThat(provider.getTorrentCount()).isEqualTo(1); } @Test public void shouldMoveTorrentFileToArchivedFolderFromInfoHash() throws IOException, NoMoreTorrentsFileAvailableException { - TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); provider.init(); - provider.onFileCreate(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent").toFile()); + provider.onFileCreate(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent").toFile()); assertThat(provider.getTorrentCount()).isEqualTo(1); - assertThat(joalFoldersPath.getTorrentArchivedPath().resolve("ubuntu.torrent")).doesNotExist(); + assertThat(joalFoldersPath.getTorrentArchiveDirPath().resolve("ubuntu.torrent")).doesNotExist(); provider.moveToArchiveFolder(provider.getTorrentFiles().get(0).getTorrentInfoHash()); - assertThat(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent")).doesNotExist(); + assertThat(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent")).doesNotExist(); - assertThat(joalFoldersPath.getTorrentArchivedPath().resolve("ubuntu.torrent")).exists(); + assertThat(joalFoldersPath.getTorrentArchiveDirPath().resolve("ubuntu.torrent")).exists(); } @Test public void shouldMoveTorrentFileToArchivedFolder() throws IOException { - final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); provider.init(); provider.onFileCreate(torrentFile.toFile()); assertThat(provider.getTorrentCount()).isEqualTo(1); - assertThat(joalFoldersPath.getTorrentArchivedPath().resolve("ubuntu.torrent")).doesNotExist(); + assertThat(joalFoldersPath.getTorrentArchiveDirPath().resolve("ubuntu.torrent")).doesNotExist(); provider.moveToArchiveFolder(torrentFile.toFile()); - assertThat(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent")).doesNotExist(); + assertThat(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent")).doesNotExist(); - assertThat(joalFoldersPath.getTorrentArchivedPath().resolve("ubuntu.torrent")).exists(); + assertThat(joalFoldersPath.getTorrentArchiveDirPath().resolve("ubuntu.torrent")).exists(); } @Test public void shouldNotFailIfFileIsNotPresentWhenArchiving() throws IOException { - final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); try { provider.moveToArchiveFolder(torrentFile.resolve("dd.torrent").toFile()); } catch (final Throwable throwable) { - fail("should not fail if file were not present."); + fail("should not fail if file were not present"); } } @Test public void shouldCallOnFileDeleteBeforeDeletingFileWhenArchiving() throws IOException { - final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); final TorrentFileProvider provider = Mockito.spy(new TorrentFileProvider(joalFoldersPath)); provider.init(); @@ -180,7 +180,7 @@ public class TorrentFileProviderTest { @Test public void shouldNotifyListenerOnFileAdded() throws IOException { - final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); provider.start(); @@ -201,7 +201,7 @@ public class TorrentFileProviderTest { @Test public void shouldNotifyListenerOnFileRemoved() throws IOException { - final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); provider.onFileCreate(torrentFile.toFile()); @@ -220,7 +220,7 @@ public class TorrentFileProviderTest { @Test public void shouldNotifyListenerOnFileChanged() throws IOException { - final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); provider.start(); @@ -240,8 +240,8 @@ public class TorrentFileProviderTest { @Test public void shouldUnRegisterListener() throws IOException { - final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); - final Path torrentFile2 = TorrentFileCreator.create(joalFoldersPath.getTorrentFilesPath().resolve("audio.torrent"), TorrentFileCreator.TorrentType.AUDIO); + final Path torrentFile = TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("ubuntu.torrent"), TorrentFileCreator.TorrentType.UBUNTU); + final Path torrentFile2 = TorrentFileCreator.create(joalFoldersPath.getTorrentsDirPath().resolve("audio.torrent"), TorrentFileCreator.TorrentType.AUDIO); final TorrentFileProvider provider = new TorrentFileProvider(joalFoldersPath); provider.start(); diff --git a/src/test/java/org/araymond/joal/core/torrent/watcher/TorrentFileWatcherTest.java b/src/test/java/org/araymond/joal/core/torrent/watcher/TorrentFileWatcherTest.java index 594e9a7..ab44fba 100644 --- a/src/test/java/org/araymond/joal/core/torrent/watcher/TorrentFileWatcherTest.java +++ b/src/test/java/org/araymond/joal/core/torrent/watcher/TorrentFileWatcherTest.java @@ -62,13 +62,6 @@ public class TorrentFileWatcherTest { .hasMessageContaining("Folder [" + torrentsPath.resolve("nop").toAbsolutePath() + "] does not exists."); } - @Test - public void shouldNotBuildWithNullInterval() { - assertThatThrownBy(() -> new TorrentFileWatcher(new FileAlterationListenerAdaptor(), torrentsPath, null)) - .isInstanceOf(NullPointerException.class) - .hasMessageContaining("intervalMs cannot be null"); - } - @Test public void shouldNotBuildWithIntervalLessThan1() { assertThatThrownBy(() -> new TorrentFileWatcher(new FileAlterationListenerAdaptor(), torrentsPath, 0)) diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/ClientTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/ClientTest.java index e012013..d1bf1f5 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/ClientTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/ClientTest.java @@ -1,6 +1,5 @@ package org.araymond.joal.core.ttorrent.client; -import com.google.common.collect.Lists; import com.turn.ttorrent.client.announce.AnnounceException; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; import org.araymond.joal.core.bandwith.BandwidthDispatcher; @@ -14,24 +13,24 @@ import org.araymond.joal.core.torrent.torrent.MockedTorrentTest; import org.araymond.joal.core.torrent.watcher.TorrentFileProvider; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFactory; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceRequest; import org.araymond.joal.core.ttorrent.client.announcer.request.AnnouncerExecutor; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import org.mockito.stubbing.Stubber; import org.springframework.context.ApplicationEventPublisher; import java.time.temporal.TemporalUnit; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -67,11 +66,12 @@ public class ClientTest { } try { if (stubber == null) { - Mockito.doThrow(new NoMoreTorrentsFileAvailableException("no more")).when(torrentFileProvider).getTorrentNotIn(anyList()); + Mockito.doThrow(new NoMoreTorrentsFileAvailableException("no more")) + .when(torrentFileProvider).getTorrentNotIn(anyCollection()); } else { stubber .doThrow(new NoMoreTorrentsFileAvailableException("no more")) - .when(torrentFileProvider).getTorrentNotIn(anyList()); + .when(torrentFileProvider).getTorrentNotIn(anyCollection()); } } catch (final NoMoreTorrentsFileAvailableException ignore) { } @@ -115,7 +115,7 @@ public class ClientTest { final AppConfiguration appConfiguration = this.createMockedConf(); doReturn(5).when(appConfiguration).getSimultaneousSeed(); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList( + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(List.of( MockedTorrentTest.createOneMock("abc"), MockedTorrentTest.createOneMock("def"), MockedTorrentTest.createOneMock("ghi"), @@ -141,7 +141,7 @@ public class ClientTest { // then Mockito.verify(delayQueue, times(appConfiguration.getSimultaneousSeed())) .addOrReplace(any(AnnounceRequest.class), anyInt(), any(TemporalUnit.class)); - assertThat(client.getCurrentlySeedingAnnouncer().stream() + assertThat(client.getCurrentlySeedingAnnouncers().stream() .map(announcer -> announcer.getTorrentInfoHash().value()) .reduce((o, t) -> o + t).get() ).isEqualTo("abcdefghijklmno"); @@ -175,16 +175,16 @@ public class ClientTest { @SuppressWarnings({"ResultOfMethodCallIgnored", "unchecked"}) @Test - public void shouldClearDelayQueueOnStopAndSendStopAnnounceToExecutor() throws AnnounceException, TooMuchAnnouncesFailedInARawException { + public void shouldClearDelayQueueOnStopAndSendStopAnnounceToExecutor() throws AnnounceException, TooManyAnnouncesFailedInARowException { final AppConfiguration appConfiguration = this.createMockedConf(); doReturn(1).when(appConfiguration).getSimultaneousSeed(); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList( + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(List.of( MockedTorrentTest.createOneMock("abc") )); final DelayQueue delayQueue = mock(DelayQueue.class); - doReturn(Lists.newArrayList(AnnounceRequest.createRegular(null))).when(delayQueue).drainAll(); + doReturn(List.of(AnnounceRequest.createRegular(null))).when(delayQueue).drainAll(); final AnnouncerFactory mockedAnnouncerFactory = mock(AnnouncerFactory.class); final Client client = (Client) ClientBuilder.builder() @@ -205,12 +205,7 @@ public class ClientTest { client.stop(); verify(delayQueue, times(1)).drainAll(); - verify(announcerExecutor, times(1)).execute(argThat(new ArgumentMatcher() { - @Override - public boolean matches(final AnnounceRequest argument) { - return argument.getEvent() == RequestEvent.STOPPED; - } - })); + verify(announcerExecutor, times(1)).execute(argThat(argument -> argument.getEvent() == RequestEvent.STOPPED)); } @Test @@ -218,7 +213,7 @@ public class ClientTest { final AppConfiguration appConfiguration = this.createMockedConf(); doReturn(1).when(appConfiguration).getSimultaneousSeed(); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList( + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(List.of( MockedTorrentTest.createOneMock("abc") )); @@ -243,12 +238,7 @@ public class ClientTest { client.stop(); verify(delayQueue, times(1)).drainAll(); - verify(announcerExecutor, times(0)).execute(argThat(new ArgumentMatcher() { - @Override - public boolean matches(final AnnounceRequest argument) { - return argument.getEvent() == RequestEvent.STOPPED; - } - })); + verify(announcerExecutor, never()).execute(argThat(argument -> argument.getEvent() == RequestEvent.STOPPED)); } @SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"}) @@ -257,7 +247,7 @@ public class ClientTest { final AppConfiguration appConfiguration = this.createMockedConf(); doReturn(0).when(appConfiguration).getSimultaneousSeed(); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList( + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(List.of( MockedTorrentTest.createOneMock("abc") )); @@ -288,7 +278,7 @@ public class ClientTest { final AppConfiguration appConfiguration = this.createMockedConf(); doReturn(1).when(appConfiguration).getSimultaneousSeed(); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList( + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(List.of( MockedTorrentTest.createOneMock("abc"), MockedTorrentTest.createOneMock("def") )); @@ -311,16 +301,16 @@ public class ClientTest { client.start(); verify(delayQueue, times(1)).addOrReplace(argumentCaptor.capture(), anyInt(), any(TemporalUnit.class)); - assertThat(client.getCurrentlySeedingAnnouncer()).hasSize(1); + assertThat(client.getCurrentlySeedingAnnouncers()).hasSize(1); final Announcer firstAnnouncer = argumentCaptor.getValue().getAnnouncer(); - client.onTooManyFailedInARaw(firstAnnouncer); + client.onTooManyFailedInARow(firstAnnouncer); verify(torrentFileProvider, times(1)).moveToArchiveFolder(eq(firstAnnouncer.getTorrentInfoHash())); verify(delayQueue, times(2)).addOrReplace(argumentCaptor.capture(), anyInt(), any(TemporalUnit.class)); assertThat(argumentCaptor.getValue().getInfoHash()).isNotEqualTo(firstAnnouncer.getTorrentInfoHash()); assertThat(argumentCaptor.getValue().getInfoHash().value()).isEqualTo("def"); - assertThat(client.getCurrentlySeedingAnnouncer()).hasSize(1); + assertThat(client.getCurrentlySeedingAnnouncers()).hasSize(1); } @SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"}) @@ -331,7 +321,7 @@ public class ClientTest { final MockedTorrent torrent = MockedTorrentTest.createOneMock("abc"); final MockedTorrent torrent2 = MockedTorrentTest.createOneMock("abc"); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList( + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(List.of( torrent, torrent2 )); @@ -352,7 +342,7 @@ public class ClientTest { client.setAnnouncerExecutor(announcerExecutor); client.start(); - assertThat(client.getCurrentlySeedingAnnouncer()).hasSize(1); + assertThat(client.getCurrentlySeedingAnnouncers()).hasSize(1); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(AnnounceRequest.class); verify(delayQueue, times(1)).addOrReplace(argumentCaptor.capture(), anyInt(), any(TemporalUnit.class)); @@ -372,7 +362,7 @@ public class ClientTest { doReturn(2).when(appConfiguration).getSimultaneousSeed(); final MockedTorrent torrent2 = MockedTorrentTest.createOneMock("def"); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList( + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(List.of( MockedTorrentTest.createOneMock("abc") )); @@ -392,7 +382,7 @@ public class ClientTest { client.setAnnouncerExecutor(announcerExecutor); client.start(); - assertThat(client.getCurrentlySeedingAnnouncer()).hasSize(1); + assertThat(client.getCurrentlySeedingAnnouncers()).hasSize(1); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(AnnounceRequest.class); @@ -405,7 +395,7 @@ public class ClientTest { assertThat(announceRequest.getInfoHash()).isEqualTo(torrent2.getTorrentInfoHash()); assertThat(announceRequest.getEvent()).isEqualTo(RequestEvent.STARTED); - assertThat(client.getCurrentlySeedingAnnouncer()).hasSize(2); + assertThat(client.getCurrentlySeedingAnnouncers()).hasSize(2); } @SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"}) @@ -415,7 +405,7 @@ public class ClientTest { doReturn(1).when(appConfiguration).getSimultaneousSeed(); final MockedTorrent torrent3 = MockedTorrentTest.createOneMock("ghi"); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList( + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(List.of( MockedTorrentTest.createOneMock("abc"), MockedTorrentTest.createOneMock("def") )); @@ -436,13 +426,13 @@ public class ClientTest { client.setAnnouncerExecutor(announcerExecutor); client.start(); - assertThat(client.getCurrentlySeedingAnnouncer()).hasSize(1); + assertThat(client.getCurrentlySeedingAnnouncers()).hasSize(1); Mockito.reset(delayQueue); client.onTorrentFileAdded(torrent3); - verify(delayQueue, times(0)).addOrReplace(any(AnnounceRequest.class), anyInt(), any(TemporalUnit.class)); + verify(delayQueue, never()).addOrReplace(any(AnnounceRequest.class), anyInt(), any(TemporalUnit.class)); - assertThat(client.getCurrentlySeedingAnnouncer()).hasSize(1); + assertThat(client.getCurrentlySeedingAnnouncers()).hasSize(1); } @@ -453,7 +443,7 @@ public class ClientTest { doReturn(1).when(appConfiguration).getSimultaneousSeed(); final MockedTorrent torrent = MockedTorrentTest.createOneMock("abc"); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList( + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(List.of( torrent )); @@ -473,7 +463,7 @@ public class ClientTest { client.setAnnouncerExecutor(announcerExecutor); client.start(); - assertThat(client.getCurrentlySeedingAnnouncer()).hasSize(1); + assertThat(client.getCurrentlySeedingAnnouncers()).hasSize(1); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(AnnounceRequest.class); @@ -493,7 +483,7 @@ public class ClientTest { final AppConfiguration appConfiguration = this.createMockedConf(); doReturn(1).when(appConfiguration).getSimultaneousSeed(); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList( + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(List.of( MockedTorrentTest.createOneMock("abc") )); @@ -513,14 +503,14 @@ public class ClientTest { client.setAnnouncerExecutor(announcerExecutor); client.start(); - assertThat(client.getCurrentlySeedingAnnouncer()).hasSize(1); + assertThat(client.getCurrentlySeedingAnnouncers()).hasSize(1); try { client.onTorrentFileRemoved(MockedTorrentTest.createOneMock("def")); } catch (final Throwable t) { fail("Should not have thrown"); } - assertThat(client.getCurrentlySeedingAnnouncer()).hasSize(1); + assertThat(client.getCurrentlySeedingAnnouncers()).hasSize(1); } @@ -529,7 +519,7 @@ public class ClientTest { public void shouldRegisterToTorrentFileProviderOnStartAndUnregisterOnStop() { final AppConfiguration appConfiguration = this.createMockedConf(); - final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(Lists.newArrayList()); + final TorrentFileProvider torrentFileProvider = createMockedTorrentFileProviderWithTorrent(new ArrayList<>()); final DelayQueue delayQueue = mock(DelayQueue.class); final AnnouncerFactory mockedAnnouncerFactory = createMockedAnnouncerFactory(); diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/ConnectionHandlerTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/ConnectionHandlerTest.java index af82fff..43dddac 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/ConnectionHandlerTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/ConnectionHandlerTest.java @@ -35,7 +35,7 @@ public class ConnectionHandlerTest { public static ConnectionHandler createMockedIpv6(final int port) { try { - final InetAddress inetAddress = InetAddress.getByName("fd2d:7212:4cd5:2f14:ffff:ffff:ffff:ffff");; + final InetAddress inetAddress = InetAddress.getByName("fd2d:7212:4cd5:2f14:ffff:ffff:ffff:ffff"); final ConnectionHandler connectionHandler = Mockito.mock(ConnectionHandler.class); Mockito.when(connectionHandler.getPort()).thenReturn(46582); Mockito.when(connectionHandler.getIpAddress()).thenReturn(inetAddress); diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java index 1f91156..e5fab7d 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java @@ -3,10 +3,16 @@ package org.araymond.joal.core.ttorrent.client.announcer; import org.apache.http.client.HttpClient; import org.araymond.joal.core.torrent.torrent.MockedTorrent; import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceDataAccessor; +import org.araymond.joal.core.ttorrent.client.announcer.tracker.NoMoreUriAvailableException; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.net.URI; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.util.Lists.list; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; public class AnnouncerFactoryTest { @@ -17,9 +23,18 @@ public class AnnouncerFactoryTest { final AnnouncerFactory announcerFactory = new AnnouncerFactory(announceDataAccessor, Mockito.mock(HttpClient.class)); final MockedTorrent torrent = mock(MockedTorrent.class); + given(torrent.getAnnounceList()).willReturn(list(list(URI.create("http://localhost")))); final Announcer announcer = announcerFactory.create(torrent); assertThat(announcer.getTorrent()).isEqualTo(torrent); } + @Test + public void createThrowsIfTorrentContainsNoValidURIs() { + final AnnounceDataAccessor announceDataAccessor = mock(AnnounceDataAccessor.class); + final AnnouncerFactory announcerFactory = new AnnouncerFactory(announceDataAccessor, Mockito.mock(HttpClient.class)); + + assertThatThrownBy(() -> announcerFactory.create(mock(MockedTorrent.class))) + .isInstanceOf(NoMoreUriAvailableException.class); + } } diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerTest.java index f896ad7..633364e 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerTest.java @@ -1,13 +1,12 @@ package org.araymond.joal.core.ttorrent.client.announcer; -import com.google.common.collect.Lists; import com.turn.ttorrent.client.announce.AnnounceException; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; import org.apache.http.client.HttpClient; import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.torrent.torrent.MockedTorrent; import org.araymond.joal.core.torrent.torrent.MockedTorrentTest; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceDataAccessor; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponseTest; @@ -16,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.time.LocalDateTime; +import java.util.HashSet; import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -38,14 +38,14 @@ public class AnnouncerTest { } @Test - public void shouldThrowTooManyFailsExceptionIfFailsFiveTimesInARaw() throws AnnounceException { + public void shouldThrowTooManyFailsExceptionIfFailsFiveTimesInARow() throws AnnounceException { final MockedTorrent torrent = MockedTorrentTest.createOneMock("abcd"); final TrackerClient trackerClient = mock(TrackerClient.class); doThrow(new AnnounceException("yeah ! :)")).when(trackerClient).announce(anyString(), any()); final AnnounceDataAccessor dataAccessor = mock(AnnounceDataAccessor.class); doReturn("dd=ff&qq=d").when(dataAccessor).getHttpRequestQueryForTorrent(any(InfoHash.class), eq(RequestEvent.STARTED)); - doReturn(Lists.newArrayList()).when(dataAccessor).getHttpHeadersForTorrent(); + doReturn(new HashSet<>()).when(dataAccessor).getHttpHeadersForTorrent(); final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class)); announcer.setTrackerClient(trackerClient); @@ -56,13 +56,13 @@ public class AnnouncerTest { announcer.announce(RequestEvent.STARTED); fail("Should have thrown AnnounceException"); } catch (final AnnounceException ignore) { - } catch (final TooMuchAnnouncesFailedInARawException e) { - fail("should not have thrown TooMuchAnnouncesFailedInARawException already"); + } catch (final TooManyAnnouncesFailedInARowException e) { + fail("should not have thrown TooManyAnnouncesFailedInARowException already"); } } assertThatThrownBy(() -> announcer.announce(RequestEvent.STARTED)) - .isInstanceOf(TooMuchAnnouncesFailedInARawException.class); + .isInstanceOf(TooManyAnnouncesFailedInARowException.class); } @Test @@ -78,7 +78,7 @@ public class AnnouncerTest { .when(trackerClient).announce(anyString(), any()); final AnnounceDataAccessor dataAccessor = mock(AnnounceDataAccessor.class); doReturn("dd=ff&qq=d").when(dataAccessor).getHttpRequestQueryForTorrent(any(InfoHash.class), eq(RequestEvent.STARTED)); - doReturn(Lists.newArrayList()).when(dataAccessor).getHttpHeadersForTorrent(); + doReturn(new HashSet<>()).when(dataAccessor).getHttpHeadersForTorrent(); final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class)); announcer.setTrackerClient(trackerClient); @@ -89,15 +89,15 @@ public class AnnouncerTest { announcer.announce(RequestEvent.STARTED); fail("Should have thrown AnnounceException"); } catch (final AnnounceException ignore) { - } catch (final TooMuchAnnouncesFailedInARawException e) { - fail("should not have thrown TooMuchAnnouncesFailedInARawException already"); + } catch (final TooManyAnnouncesFailedInARowException e) { + fail("should not have thrown TooManyAnnouncesFailedInARowException already"); } } assertThat(announcer.getConsecutiveFails()).isEqualTo(4); try { announcer.announce(RequestEvent.STARTED); - } catch (final TooMuchAnnouncesFailedInARawException | AnnounceException e) { + } catch (final TooManyAnnouncesFailedInARowException | AnnounceException e) { fail("should not have failed"); } @@ -114,7 +114,7 @@ public class AnnouncerTest { .when(trackerClient).announce(anyString(), any()); final AnnounceDataAccessor dataAccessor = mock(AnnounceDataAccessor.class); doReturn("dd=ff&qq=d").when(dataAccessor).getHttpRequestQueryForTorrent(any(InfoHash.class), eq(RequestEvent.STARTED)); - doReturn(Lists.newArrayList()).when(dataAccessor).getHttpHeadersForTorrent(); + doReturn(new HashSet<>()).when(dataAccessor).getHttpHeadersForTorrent(); final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class)); announcer.setTrackerClient(trackerClient); @@ -123,7 +123,7 @@ public class AnnouncerTest { try { announcer.announce(RequestEvent.STARTED); fail("should have failed"); - } catch (final TooMuchAnnouncesFailedInARawException | AnnounceException ignore) { + } catch (final TooManyAnnouncesFailedInARowException | AnnounceException ignore) { } //noinspection ConstantConditions final LocalDateTime lastDateTime = announcer.getLastAnnouncedAt().get(); @@ -134,7 +134,7 @@ public class AnnouncerTest { try { announcer.announce(RequestEvent.STARTED); - } catch (final TooMuchAnnouncesFailedInARawException | AnnounceException ignore) { + } catch (final TooManyAnnouncesFailedInARowException | AnnounceException ignore) { fail("should not have failed"); } //noinspection ConstantConditions @@ -153,7 +153,7 @@ public class AnnouncerTest { .when(trackerClient).announce(anyString(), any()); final AnnounceDataAccessor dataAccessor = mock(AnnounceDataAccessor.class); doReturn("dd=ff&qq=d").when(dataAccessor).getHttpRequestQueryForTorrent(any(InfoHash.class), eq(RequestEvent.STARTED)); - doReturn(Lists.newArrayList()).when(dataAccessor).getHttpHeadersForTorrent(); + doReturn(new HashSet<>()).when(dataAccessor).getHttpHeadersForTorrent(); final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class)); announcer.setTrackerClient(trackerClient); @@ -163,7 +163,7 @@ public class AnnouncerTest { assertThat(announcer.getLastKnownLeechers()).isEmpty(); try { announcer.announce(RequestEvent.STARTED); - } catch (final TooMuchAnnouncesFailedInARawException | AnnounceException ignore) { + } catch (final TooManyAnnouncesFailedInARowException | AnnounceException ignore) { fail("should not have failed"); } assertThat(announcer.getLastKnownInterval()).isEqualTo(1800); @@ -173,7 +173,7 @@ public class AnnouncerTest { try { announcer.announce(RequestEvent.STARTED); fail("should have failed"); - } catch (final TooMuchAnnouncesFailedInARawException | AnnounceException ignore) { + } catch (final TooManyAnnouncesFailedInARowException | AnnounceException ignore) { } // same after a fail assertThat(announcer.getLastKnownInterval()).isEqualTo(1800); @@ -182,7 +182,7 @@ public class AnnouncerTest { try { announcer.announce(RequestEvent.STARTED); - } catch (final TooMuchAnnouncesFailedInARawException | AnnounceException ignore) { + } catch (final TooManyAnnouncesFailedInARowException | AnnounceException ignore) { fail("should not have failed"); } assertThat(announcer.getLastKnownInterval()).isEqualTo(900); diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooMuchAnnouncesFailedInARawExceptionTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooManyAnnouncesFailedInARowExceptionTest.java similarity index 72% rename from src/test/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooMuchAnnouncesFailedInARawExceptionTest.java rename to src/test/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooManyAnnouncesFailedInARowExceptionTest.java index 89d6fb6..1c49a1a 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooMuchAnnouncesFailedInARawExceptionTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/exceptions/TooManyAnnouncesFailedInARowExceptionTest.java @@ -6,12 +6,12 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -public class TooMuchAnnouncesFailedInARawExceptionTest { +public class TooManyAnnouncesFailedInARowExceptionTest { @Test public void shouldBuild() { final MockedTorrent torrent = mock(MockedTorrent.class); - final TooMuchAnnouncesFailedInARawException exception = new TooMuchAnnouncesFailedInARawException(torrent); + final TooManyAnnouncesFailedInARowException exception = new TooManyAnnouncesFailedInARowException(torrent); assertThat(exception.getTorrent()).isEqualTo(torrent); } diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessorTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessorTest.java index 67f885a..f78921d 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessorTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessorTest.java @@ -1,6 +1,5 @@ package org.araymond.joal.core.ttorrent.client.announcer.request; -import com.turn.ttorrent.common.protocol.TrackerMessage; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; import org.araymond.joal.core.bandwith.BandwidthDispatcher; import org.araymond.joal.core.bandwith.TorrentSeedStats; @@ -39,7 +38,7 @@ public class AnnounceDataAccessorTest { } @Test - public void shouldCallCreateHeadersOnBitTorrentClient() { + public void shouldCallGetHeadersOnBitTorrentClient() { final BitTorrentClient bitTorrentClient = mock(BitTorrentClient.class); final BandwidthDispatcher bandwidthDispatcher = mock(BandwidthDispatcher.class); final ConnectionHandler connectionHandler = mock(ConnectionHandler.class); @@ -48,7 +47,7 @@ public class AnnounceDataAccessorTest { announceDataAccessor.getHttpHeadersForTorrent(); - verify(bitTorrentClient, times(1)).createRequestHeaders(); + verify(bitTorrentClient, times(1)).getHeaders(); } } diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnouncerExecutorTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnouncerExecutorTest.java index a6a0cd5..fe635d1 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnouncerExecutorTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnouncerExecutorTest.java @@ -5,7 +5,7 @@ import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.R import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.torrent.torrent.MockedTorrent; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.response.AnnounceResponseCallback; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -25,7 +25,7 @@ import static org.mockito.Mockito.mock; public class AnnouncerExecutorTest { @Test - public void shouldNotExecuteMoreThanThreeConcurentThreads() throws InterruptedException, AnnounceException, TooMuchAnnouncesFailedInARawException { + public void shouldNotExecuteMoreThanThreeConcurrentThreads() throws Exception { final AnnouncerExecutor executor = new AnnouncerExecutor(new DefaultCallback()); final AtomicInteger atomicInteger = new AtomicInteger(0); @@ -35,8 +35,8 @@ public class AnnouncerExecutorTest { Mockito.doAnswer(invocation -> { atomicInteger.incrementAndGet(); try { - // Thread that nevers dies when started - Thread.sleep(90000); + // Thread that never dies when started + Thread.sleep(90_000); } catch (final InterruptedException ignored) { } return null; @@ -50,7 +50,7 @@ public class AnnouncerExecutorTest { } @Test - public void shouldCallCallbackAfterExecution() throws InterruptedException, AnnounceException, TooMuchAnnouncesFailedInARawException { + public void shouldCallCallbackAfterExecution() throws Exception { final CountDownLatch countDown = new CountDownLatch(100); final AnnounceResponseCallback announceResponseCallback = new DefaultCallback() { @Override @@ -72,7 +72,7 @@ public class AnnouncerExecutorTest { } @Test - public void shouldCallOnAnnounceFailureWhenAnnounceThrownException() throws InterruptedException, AnnounceException, TooMuchAnnouncesFailedInARawException { + public void shouldCallOnAnnounceFailureWhenAnnounceThrownException() throws Exception { final CountDownLatch countDown = new CountDownLatch(100); final AnnounceResponseCallback announceResponseCallback = new DefaultCallback() { @Override @@ -94,11 +94,11 @@ public class AnnouncerExecutorTest { } @Test - public void shouldCallTooManyFailsWhenAnnounceThrownTooManyFails() throws InterruptedException, AnnounceException, TooMuchAnnouncesFailedInARawException { + public void shouldCallTooManyFailsWhenAnnounceThrownTooManyFails() throws Exception { final CountDownLatch countDown = new CountDownLatch(100); final AnnounceResponseCallback announceResponseCallback = new DefaultCallback() { @Override - public void onTooManyAnnounceFailedInARaw(final RequestEvent event, final Announcer announcer, final TooMuchAnnouncesFailedInARawException e) { + public void onTooManyAnnounceFailedInARow(final RequestEvent event, final Announcer announcer, final TooManyAnnouncesFailedInARowException e) { countDown.countDown(); } }; @@ -107,7 +107,7 @@ public class AnnouncerExecutorTest { for (int i = 0; i < 100; i++) { final Announcer announcer = mock(Announcer.class); Mockito.doReturn(new InfoHash(ByteBuffer.allocate(4).putInt(i).array())).when(announcer).getTorrentInfoHash(); - Mockito.doThrow(new TooMuchAnnouncesFailedInARawException(mock(MockedTorrent.class))).when(announcer).announce(Mockito.any()); + Mockito.doThrow(new TooManyAnnouncesFailedInARowException(mock(MockedTorrent.class))).when(announcer).announce(Mockito.any()); executor.execute(AnnounceRequest.createRegular(announcer)); } @@ -116,7 +116,7 @@ public class AnnouncerExecutorTest { } @Test - public void shouldDenyAThread() throws InterruptedException, AnnounceException, TooMuchAnnouncesFailedInARawException { + public void shouldDenyAThread() throws Exception { final AtomicInteger atomicInteger = new AtomicInteger(0); final AnnounceResponseCallback announceResponseCallback = new DefaultCallback() { @Override @@ -160,7 +160,7 @@ public class AnnouncerExecutorTest { } @Test - public void shouldDenyAll() throws InterruptedException, AnnounceException, TooMuchAnnouncesFailedInARawException { + public void shouldDenyAll() throws Exception { final AtomicInteger atomicInteger = new AtomicInteger(0); final AnnounceResponseCallback announceResponseCallback = new DefaultCallback() { @Override @@ -191,11 +191,11 @@ public class AnnouncerExecutorTest { assertThat(executor.denyAll()).hasSize(100); assertThat(atomicInteger.get()).isEqualTo(0); - assertThat(executor.denyAll()).hasSize(0); // after being denied, the list of running thread should be empty + assertThat(executor.denyAll()).isEmpty(); // after being denied, the list of running thread should be empty } @Test - public void shouldAwaitAllThreadToFinishBeforeReturningFromAwait() throws AnnounceException, TooMuchAnnouncesFailedInARawException { + public void shouldAwaitAllThreadToFinishBeforeReturningFromAwait() throws AnnounceException, TooManyAnnouncesFailedInARowException { final AtomicInteger atomicInteger = new AtomicInteger(0); final AnnounceResponseCallback announceResponseCallback = new DefaultCallback() { @@ -242,7 +242,7 @@ public class AnnouncerExecutorTest { } @Override - public void onTooManyAnnounceFailedInARaw(final RequestEvent event, final Announcer announcer, final TooMuchAnnouncesFailedInARawException e) { + public void onTooManyAnnounceFailedInARow(final RequestEvent event, final Announcer announcer, final TooManyAnnouncesFailedInARowException e) { } } diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceEventPublisherTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceEventPublisherTest.java index 4b546e2..28c3067 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceEventPublisherTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceEventPublisherTest.java @@ -8,7 +8,7 @@ import org.araymond.joal.core.events.announce.WillAnnounceEvent; import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.torrent.torrent.MockedTorrent; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -169,7 +169,7 @@ public class AnnounceEventPublisherTest { final AnnounceEventPublisher notifier = new AnnounceEventPublisher(appEventPublisher); final Announcer announcer = mock(Announcer.class); Mockito.doReturn(new InfoHash("ddd".getBytes())).when(announcer).getTorrentInfoHash(); - notifier.onTooManyAnnounceFailedInARaw(announcer, new TooMuchAnnouncesFailedInARawException(mock(MockedTorrent.class))); + notifier.onTooManyAnnounceFailedInARow(announcer, new TooManyAnnouncesFailedInARowException(mock(MockedTorrent.class))); final ArgumentCaptor captor = ArgumentCaptor.forClass(TooManyAnnouncesFailedEvent.class); diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceReEnqueuerTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceReEnqueuerTest.java index 1e03356..89c71f4 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceReEnqueuerTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceReEnqueuerTest.java @@ -5,7 +5,7 @@ import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.torrent.torrent.MockedTorrent; import org.araymond.joal.core.ttorrent.client.DelayQueue; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceRequest; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; import org.junit.jupiter.api.Test; @@ -151,7 +151,7 @@ public class AnnounceReEnqueuerTest { final AnnounceReEnqueuer announceReEnqueuer = new AnnounceReEnqueuer(delayQueue); - announceReEnqueuer.onTooManyAnnounceFailedInARaw(announcer, new TooMuchAnnouncesFailedInARawException(Mockito.mock(MockedTorrent.class))); + announceReEnqueuer.onTooManyAnnounceFailedInARow(announcer, new TooManyAnnouncesFailedInARowException(Mockito.mock(MockedTorrent.class))); Mockito.verifyNoMoreInteractions(delayQueue); } diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChainTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChainTest.java index b3d311b..b0e1b81 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChainTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/AnnounceResponseHandlerChainTest.java @@ -3,7 +3,7 @@ package org.araymond.joal.core.ttorrent.client.announcer.response; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; import org.araymond.joal.core.torrent.torrent.MockedTorrent; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -41,8 +41,8 @@ public class AnnounceResponseHandlerChainTest { @Test public void shouldDispatchOnWillAnnounce() { - final AnnounceResponseHandlerChainElement e1 = mock(AnnounceResponseHandlerChainElement.class); - final AnnounceResponseHandlerChainElement e2 = mock(AnnounceResponseHandlerChainElement.class); + final AnnounceResponseHandler e1 = mock(AnnounceResponseHandler.class); + final AnnounceResponseHandler e2 = mock(AnnounceResponseHandler.class); final AnnounceResponseHandlerChain announceResponseHandlerChain = new AnnounceResponseHandlerChain(); announceResponseHandlerChain.appendHandler(e1); @@ -59,8 +59,8 @@ public class AnnounceResponseHandlerChainTest { @Test public void shouldDispatchSuccessAnnounceStart() { - final AnnounceResponseHandlerChainElement e1 = mock(AnnounceResponseHandlerChainElement.class); - final AnnounceResponseHandlerChainElement e2 = mock(AnnounceResponseHandlerChainElement.class); + final AnnounceResponseHandler e1 = mock(AnnounceResponseHandler.class); + final AnnounceResponseHandler e2 = mock(AnnounceResponseHandler.class); final AnnounceResponseHandlerChain announceResponseHandlerChain = new AnnounceResponseHandlerChain(); announceResponseHandlerChain.appendHandler(e1); @@ -78,8 +78,8 @@ public class AnnounceResponseHandlerChainTest { @Test public void shouldDispatchSuccessAnnounceRegular() { - final AnnounceResponseHandlerChainElement e1 = mock(AnnounceResponseHandlerChainElement.class); - final AnnounceResponseHandlerChainElement e2 = mock(AnnounceResponseHandlerChainElement.class); + final AnnounceResponseHandler e1 = mock(AnnounceResponseHandler.class); + final AnnounceResponseHandler e2 = mock(AnnounceResponseHandler.class); final AnnounceResponseHandlerChain announceResponseHandlerChain = new AnnounceResponseHandlerChain(); announceResponseHandlerChain.appendHandler(e1); @@ -97,8 +97,8 @@ public class AnnounceResponseHandlerChainTest { @Test public void shouldDispatchSuccessAnnounceStop() { - final AnnounceResponseHandlerChainElement e1 = mock(AnnounceResponseHandlerChainElement.class); - final AnnounceResponseHandlerChainElement e2 = mock(AnnounceResponseHandlerChainElement.class); + final AnnounceResponseHandler e1 = mock(AnnounceResponseHandler.class); + final AnnounceResponseHandler e2 = mock(AnnounceResponseHandler.class); final AnnounceResponseHandlerChain announceResponseHandlerChain = new AnnounceResponseHandlerChain(); announceResponseHandlerChain.appendHandler(e1); @@ -116,8 +116,8 @@ public class AnnounceResponseHandlerChainTest { @Test public void shouldDispatchFailAnnounceStart() { - final AnnounceResponseHandlerChainElement e1 = mock(AnnounceResponseHandlerChainElement.class); - final AnnounceResponseHandlerChainElement e2 = mock(AnnounceResponseHandlerChainElement.class); + final AnnounceResponseHandler e1 = mock(AnnounceResponseHandler.class); + final AnnounceResponseHandler e2 = mock(AnnounceResponseHandler.class); final AnnounceResponseHandlerChain announceResponseHandlerChain = new AnnounceResponseHandlerChain(); announceResponseHandlerChain.appendHandler(e1); @@ -134,8 +134,8 @@ public class AnnounceResponseHandlerChainTest { @Test public void shouldDispatchFailAnnounceRegular() { - final AnnounceResponseHandlerChainElement e1 = mock(AnnounceResponseHandlerChainElement.class); - final AnnounceResponseHandlerChainElement e2 = mock(AnnounceResponseHandlerChainElement.class); + final AnnounceResponseHandler e1 = mock(AnnounceResponseHandler.class); + final AnnounceResponseHandler e2 = mock(AnnounceResponseHandler.class); final AnnounceResponseHandlerChain announceResponseHandlerChain = new AnnounceResponseHandlerChain(); announceResponseHandlerChain.appendHandler(e1); @@ -152,8 +152,8 @@ public class AnnounceResponseHandlerChainTest { @Test public void shouldDispatchFailAnnounceStop() { - final AnnounceResponseHandlerChainElement e1 = mock(AnnounceResponseHandlerChainElement.class); - final AnnounceResponseHandlerChainElement e2 = mock(AnnounceResponseHandlerChainElement.class); + final AnnounceResponseHandler e1 = mock(AnnounceResponseHandler.class); + final AnnounceResponseHandler e2 = mock(AnnounceResponseHandler.class); final AnnounceResponseHandlerChain announceResponseHandlerChain = new AnnounceResponseHandlerChain(); announceResponseHandlerChain.appendHandler(e1); @@ -170,8 +170,8 @@ public class AnnounceResponseHandlerChainTest { @Test public void shouldDispatchTooManyFails() { - final AnnounceResponseHandlerChainElement e1 = mock(AnnounceResponseHandlerChainElement.class); - final AnnounceResponseHandlerChainElement e2 = mock(AnnounceResponseHandlerChainElement.class); + final AnnounceResponseHandler e1 = mock(AnnounceResponseHandler.class); + final AnnounceResponseHandler e2 = mock(AnnounceResponseHandler.class); final AnnounceResponseHandlerChain announceResponseHandlerChain = new AnnounceResponseHandlerChain(); announceResponseHandlerChain.appendHandler(e1); @@ -179,15 +179,15 @@ public class AnnounceResponseHandlerChainTest { final Announcer announcer = mock(Announcer.class); - announceResponseHandlerChain.onTooManyAnnounceFailedInARaw(RequestEvent.STARTED, announcer, new TooMuchAnnouncesFailedInARawException(mock(MockedTorrent.class))); + announceResponseHandlerChain.onTooManyAnnounceFailedInARow(RequestEvent.STARTED, announcer, new TooManyAnnouncesFailedInARowException(mock(MockedTorrent.class))); - Mockito.verify(e1, times(1)).onTooManyAnnounceFailedInARaw(eq(announcer), any(TooMuchAnnouncesFailedInARawException.class)); - Mockito.verify(e2, times(1)).onTooManyAnnounceFailedInARaw(eq(announcer), any(TooMuchAnnouncesFailedInARawException.class)); + Mockito.verify(e1, times(1)).onTooManyAnnounceFailedInARow(eq(announcer), any(TooManyAnnouncesFailedInARowException.class)); + Mockito.verify(e2, times(1)).onTooManyAnnounceFailedInARow(eq(announcer), any(TooManyAnnouncesFailedInARowException.class)); Mockito.verifyNoMoreInteractions(e1, e2); } - private static class DefaultResponseHandler implements AnnounceResponseHandlerChainElement { + private static class DefaultResponseHandler implements AnnounceResponseHandler { @Override public void onAnnouncerWillAnnounce(final Announcer announcer, final RequestEvent event) { } @@ -217,7 +217,7 @@ public class AnnounceResponseHandlerChainTest { } @Override - public void onTooManyAnnounceFailedInARaw(final Announcer announcer, final TooMuchAnnouncesFailedInARawException e) { + public void onTooManyAnnounceFailedInARow(final Announcer announcer, final TooManyAnnouncesFailedInARowException e) { } } diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/BandwidthDispatcherNotifierTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/BandwidthDispatcherNotifierTest.java index fbd19fb..3eb2eeb 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/BandwidthDispatcherNotifierTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/BandwidthDispatcherNotifierTest.java @@ -4,7 +4,7 @@ import org.araymond.joal.core.bandwith.BandwidthDispatcher; import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.torrent.torrent.MockedTorrent; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; @@ -120,7 +120,7 @@ public class BandwidthDispatcherNotifierTest { @SuppressWarnings({"ResultOfMethodCallIgnored", "TypeMayBeWeakened"}) @Test - public void shouldUnregisterOnTooManyFailsInARaw() { + public void shouldUnregisterOnTooManyFailsInARow() { final BandwidthDispatcher dispatcher = mock(BandwidthDispatcher.class); final BandwidthDispatcherNotifier notifier = new BandwidthDispatcherNotifier(dispatcher); @@ -128,7 +128,7 @@ public class BandwidthDispatcherNotifierTest { final InfoHash infoHash = new InfoHash("qjfqjbqdui".getBytes()); final Announcer announcer = mock(Announcer.class); doReturn(infoHash).when(announcer).getTorrentInfoHash(); - notifier.onTooManyAnnounceFailedInARaw(announcer, new TooMuchAnnouncesFailedInARawException(mock(MockedTorrent.class))); + notifier.onTooManyAnnounceFailedInARow(announcer, new TooManyAnnouncesFailedInARowException(mock(MockedTorrent.class))); Mockito.verify(dispatcher, times(1)).unregisterTorrent(ArgumentMatchers.eq(infoHash)); Mockito.verifyNoMoreInteractions(dispatcher); diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifierTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifierTest.java index 0ba70b1..97327fc 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifierTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifierTest.java @@ -4,7 +4,7 @@ import org.araymond.joal.core.torrent.torrent.InfoHash; import org.araymond.joal.core.torrent.torrent.MockedTorrent; import org.araymond.joal.core.ttorrent.client.Client; import org.araymond.joal.core.ttorrent.client.announcer.Announcer; -import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnouncesFailedInARawException; +import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooManyAnnouncesFailedInARowException; import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; @@ -18,9 +18,7 @@ public class ClientNotifierTest { @Test public void shouldNotifyNoMorePeersIfAnnounceStartedResponseHasNoLeechers() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); final InfoHash infoHash = new InfoHash("qjfqjbqdui".getBytes()); final Announcer announcer = mock(Announcer.class); @@ -38,9 +36,7 @@ public class ClientNotifierTest { @Test public void shouldNotifyNoMorePeersIfAnnounceStartedResponseHasNoSeeders() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); final InfoHash infoHash = new InfoHash("qjfqjbqdui".getBytes()); final Announcer announcer = mock(Announcer.class); @@ -59,9 +55,7 @@ public class ClientNotifierTest { @Test public void shouldDoNothingIfAnnounceStartedHasPeers() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); final InfoHash infoHash = new InfoHash("qjfqjbqdui".getBytes()); final Announcer announcer = mock(Announcer.class); @@ -79,9 +73,7 @@ public class ClientNotifierTest { @Test public void shouldNotifyNoMorePeersIfAnnounceRegularResponseHasNoLeechers() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); final InfoHash infoHash = new InfoHash("qjfqjbqdui".getBytes()); final Announcer announcer = mock(Announcer.class); @@ -99,9 +91,7 @@ public class ClientNotifierTest { @Test public void shouldNotifyNoMorePeersIfAnnounceRegularResponseHasNoSeeders() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); final InfoHash infoHash = new InfoHash("qjfqjbqdui".getBytes()); final Announcer announcer = mock(Announcer.class); @@ -120,9 +110,7 @@ public class ClientNotifierTest { @Test public void shouldDoNothingIfAnnounceRegularHasPeers() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); final InfoHash infoHash = new InfoHash("qjfqjbqdui".getBytes()); final Announcer announcer = mock(Announcer.class); @@ -138,9 +126,7 @@ public class ClientNotifierTest { @Test public void shouldNotifyTorrentHasStoppedOnStopSuccess() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); final Announcer announcer = mock(Announcer.class); @@ -153,15 +139,13 @@ public class ClientNotifierTest { @Test public void shouldNotifyTorrentFailedTooManyTimes() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); final Announcer announcer = mock(Announcer.class); - clientNotifier.onTooManyAnnounceFailedInARaw(announcer, new TooMuchAnnouncesFailedInARawException(mock(MockedTorrent.class))); + clientNotifier.onTooManyAnnounceFailedInARow(announcer, new TooManyAnnouncesFailedInARowException(mock(MockedTorrent.class))); - Mockito.verify(client, times(1)).onTooManyFailedInARaw(ArgumentMatchers.eq(announcer)); + Mockito.verify(client, times(1)).onTooManyFailedInARow(ArgumentMatchers.eq(announcer)); Mockito.verifyNoMoreInteractions(client); } @@ -169,9 +153,7 @@ public class ClientNotifierTest { @Test public void shouldDoNothingOnWillAnnounce() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); clientNotifier.onAnnouncerWillAnnounce(null, null); Mockito.verifyNoMoreInteractions(client); @@ -181,9 +163,7 @@ public class ClientNotifierTest { @Test public void shouldDoNothingOnStartFails() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); clientNotifier.onAnnounceStartFails(null, null); Mockito.verifyNoMoreInteractions(client); @@ -193,9 +173,7 @@ public class ClientNotifierTest { @Test public void shouldDoNothingOnRegularFails() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); clientNotifier.onAnnounceRegularFails(null, null); Mockito.verifyNoMoreInteractions(client); @@ -205,9 +183,7 @@ public class ClientNotifierTest { @Test public void shouldDoNothingOnStopFailes() { final Client client = mock(Client.class); - - final ClientNotifier clientNotifier = new ClientNotifier(); - clientNotifier.setClient(client); + final ClientNotifier clientNotifier = new ClientNotifier(client); clientNotifier.onAnnounceStopFails(null, null); Mockito.verifyNoMoreInteractions(client); diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientTest.java index 25b0a84..fbb7a61 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientTest.java @@ -19,8 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.*; public class TrackerClientTest { @@ -52,35 +51,7 @@ public class TrackerClientTest { } @Test - public void shouldIterateOverUriProviderUntilHttpSchemeIsFound() throws AnnounceException, NoMoreUriAvailableException { - final TrackerClientUriProvider uriProvider = Mockito.spy(TrackerClientUriProviderTest.createOne( - "udp://tracker.coppersurfer.tk:80/announce", - "udp://tracker.coppersurfer.tk:6969/announce", - "udp://tracker.opentrackr.org:1337/announce", - "udp://9.rarbg.me:2750/announce", - "udp://9.rarbg.com:2730/announce", - "udp://9.rarbg.to:2770/announce", - "udp://tracker.pirateparty.gr:6969/announce", - "udp://public.popcorn-tracker.org:6969/announce", - "udp://tracker.internetwarriors.net:1337/announce", - "udp://tracker.vanitycore.co:6969/announce", - "udp://tracker.zer0day.to:1337/announce", - "udp://open.stealth.si:80/announce", - "http://localhost" - )); - - final TrackerClient trackerClient = Mockito.spy(new TrackerClient(uriProvider, mock(TrackerResponseHandler.class), Mockito.mock(HttpClient.class))); - Mockito.doReturn( - this.createMockedTrackerSuccessMessage() - ).when(trackerClient).makeCallAndGetResponseAsByteBuffer(any(URI.class), anyString(), any()); - - trackerClient.announce("param=val&dd=q", this.createHeaders()); - - Mockito.verify(uriProvider, times(12)).deleteCurrentAndMoveToNext(); - } - - @Test - public void shouldAlsoAcceptHttps() throws AnnounceException, NoMoreUriAvailableException { + public void shouldAlsoAcceptHttps() throws Exception { final TrackerClientUriProvider uriProvider = Mockito.spy(TrackerClientUriProviderTest.createOne("https://localhost")); final TrackerClient trackerClient = Mockito.spy(new TrackerClient(uriProvider, mock(TrackerResponseHandler.class), Mockito.mock(HttpClient.class))); @@ -90,24 +61,11 @@ public class TrackerClientTest { trackerClient.announce("param=val&dd=q", this.createHeaders()); - Mockito.verify(uriProvider, times(0)).deleteCurrentAndMoveToNext(); + Mockito.verify(uriProvider, never()).deleteCurrentAndMoveToNext(); } @Test - public void shouldThrowExceptionIfNoHTTPUriAreFound() throws NoMoreUriAvailableException { - final TrackerClientUriProvider uriProvider = Mockito.spy(TrackerClientUriProviderTest.createOne("udp://localhost", "udp://127.0.0.1")); - - final TrackerClient trackerClient = Mockito.spy(new TrackerClient(uriProvider, mock(TrackerResponseHandler.class), Mockito.mock(HttpClient.class))); - - assertThatThrownBy(() -> trackerClient.announce("param=val&dd=q", this.createHeaders())) - .isInstanceOf(AnnounceException.class) - .hasCauseInstanceOf(NoMoreUriAvailableException.class); - - Mockito.verify(uriProvider, times(2)).deleteCurrentAndMoveToNext(); - } - - @Test - public void shouldThrowAnnounceExceptionAndMoveToNextUriWhenResponseIsError() throws AnnounceException, NoMoreUriAvailableException { + public void shouldThrowAnnounceExceptionAndMoveToNextUriWhenResponseIsError() throws Exception { final TrackerClientUriProvider uriProvider = Mockito.spy(TrackerClientUriProviderTest.createOne("http://localhost", "https://localhost")); final TrackerClient trackerClient = Mockito.spy(new TrackerClient(uriProvider, mock(TrackerResponseHandler.class), Mockito.mock(HttpClient.class))); @@ -118,12 +76,12 @@ public class TrackerClientTest { assertThatThrownBy(() -> trackerClient.announce("http://localhost", this.createHeaders())) .isInstanceOf(AnnounceException.class); - Mockito.verify(uriProvider, times(0)).deleteCurrentAndMoveToNext(); + Mockito.verify(uriProvider, never()).deleteCurrentAndMoveToNext(); Mockito.verify(uriProvider, times(1)).moveToNext(); } @Test - public void shouldRemoveOneFromSeeders() throws AnnounceException { + public void shouldRemoveOneFromSeeders() throws Exception { // should remove one seeder because we are one of them final TrackerClientUriProvider uriProvider = Mockito.spy(TrackerClientUriProviderTest.createOne("https://localhost")); @@ -141,7 +99,7 @@ public class TrackerClientTest { } @Test - public void shouldNotFailToRemoveOneFromSeedersIfThereIsNoSeeders() throws AnnounceException { + public void shouldNotFailToRemoveOneFromSeedersIfThereIsNoSeeders() throws Exception { // should remove one seeder because we are one of them final TrackerClientUriProvider uriProvider = Mockito.spy(TrackerClientUriProviderTest.createOne("https://localhost")); diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientUriProviderTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientUriProviderTest.java index 47f73f7..3907f59 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientUriProviderTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/tracker/TrackerClientUriProviderTest.java @@ -3,19 +3,20 @@ package org.araymond.joal.core.ttorrent.client.announcer.tracker; import org.junit.jupiter.api.Test; import java.net.URI; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TrackerClientUriProviderTest { public static TrackerClientUriProvider createOne(final String... uris) { - final List uriList = new ArrayList<>(); - for (final String uri : uris) { - uriList.add(URI.create(uri)); - } + final List uriList = Arrays.stream(uris) + .map(URI::create) + .collect(toList()); + return new TrackerClientUriProvider(uriList); } @@ -28,22 +29,22 @@ public class TrackerClientUriProviderTest { @Test public void shouldMoveToNext() throws NoMoreUriAvailableException { - final TrackerClientUriProvider provider = createOne("http://localhost", "https://localhost", "http://127.0.0.1", "https://127.0.0.1"); + final TrackerClientUriProvider provider = createOne("http://first-element", "https://localhost", "http://127.0.0.1", "https://127.0.0.1"); - provider.moveToNext(); - assertThat(provider.get().toString()).isEqualTo("http://localhost"); + assertThat(provider.get().toString()).isEqualTo("http://first-element"); provider.moveToNext(); assertThat(provider.get().toString()).isEqualTo("https://localhost"); provider.moveToNext(); assertThat(provider.get().toString()).isEqualTo("http://127.0.0.1"); provider.moveToNext(); assertThat(provider.get().toString()).isEqualTo("https://127.0.0.1"); + provider.moveToNext(); + assertThat(provider.get().toString()).isEqualTo("http://first-element"); } @Test public void shouldDeleteAndMoveToNext() throws NoMoreUriAvailableException { final TrackerClientUriProvider provider = createOne("http://localhost", "https://localhost", "http://127.0.0.1", "https://127.0.0.1"); - provider.deleteCurrentAndMoveToNext(); provider.deleteCurrentAndMoveToNext(); assertThat(provider.get().toString()).isEqualTo("http://127.0.0.1"); @@ -65,8 +66,40 @@ public class TrackerClientUriProviderTest { for (int i = 0; i < 20; i++) { provider.moveToNext(); - assertThat(provider.get().toString()).isEqualTo(i % 2 == 0 ? "http://localhost" : "https://localhost"); + assertThat(provider.get().toString()).isEqualTo(i % 2 == 0 ? "https://localhost" : "http://localhost"); } } + @Test + public void shouldRemoveNonHttpUris() throws NoMoreUriAvailableException { + final TrackerClientUriProvider uriProvider = createOne( + "udp://tracker.coppersurfer.tk:80/announce", + "udp://tracker.coppersurfer.tk:6969/announce", + "udp://tracker.opentrackr.org:1337/announce", + "udp://9.rarbg.me:2750/announce", + "udp://9.rarbg.com:2730/announce", + "udp://9.rarbg.to:2770/announce", + "udp://tracker.pirateparty.gr:6969/announce", + "https://localhost2", + "udp://public.popcorn-tracker.org:6969/announce", + "udp://tracker.internetwarriors.net:1337/announce", + "udp://tracker.vanitycore.co:6969/announce", + "udp://tracker.zer0day.to:1337/announce", + "udp://open.stealth.si:80/announce", + "http://localhost" + ); + + assertThat(uriProvider.get()).isEqualTo(URI.create("https://localhost2")); + assertThat(uriProvider.get()).isEqualTo(URI.create("https://localhost2")); + uriProvider.moveToNext(); + assertThat(uriProvider.get()).isEqualTo(URI.create("http://localhost")); + uriProvider.moveToNext(); + assertThat(uriProvider.get()).isEqualTo(URI.create("https://localhost2")); // started from the top again + } + + @Test + public void shouldThrowExceptionIfNoHTTPUriAreFound() { + assertThatThrownBy(() -> createOne("udp://localhost", "udp://127.0.0.1")) + .isInstanceOf(NoMoreUriAvailableException.class); + } } diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/utils/DelayQueueTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/utils/DelayQueueTest.java index 1183c6d..23f2705 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/utils/DelayQueueTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/utils/DelayQueueTest.java @@ -10,9 +10,10 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.stream.Collectors; import java.util.stream.IntStream; +import static java.lang.String.valueOf; +import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; public class DelayQueueTest { @@ -29,7 +30,7 @@ public class DelayQueueTest { queue.addOrReplace(createInfoHashAble("four"), 1801, ChronoUnit.SECONDS); queue.addOrReplace(createInfoHashAble("three"), 30, ChronoUnit.MINUTES); - final List announcers = queue.drainAll().stream().map(i -> i.getInfoHash().value()).collect(Collectors.toList());; + final List announcers = queue.drainAll().stream().map(i -> i.getInfoHash().value()).collect(toList()); assertThat(announcers).containsExactly("one", "two", "three", "four"); assertThat(queue.drainAll()).isEmpty(); } @@ -43,7 +44,7 @@ public class DelayQueueTest { queue.addOrReplace(createInfoHashAble("three"), 30, ChronoUnit.MINUTES); queue.addOrReplace(createInfoHashAble("four"), 1801, ChronoUnit.SECONDS); - final List announcers = queue.getAvailables().stream().map(i -> i.getInfoHash().value()).collect(Collectors.toList()); + final List announcers = queue.getAvailables().stream().map(i -> i.getInfoHash().value()).collect(toList()); assertThat(announcers).hasSize(2); assertThat(announcers).containsExactly("one", "two"); } @@ -59,7 +60,7 @@ public class DelayQueueTest { queue.remove(createInfoHashAble("two")); - final List announcers = queue.drainAll().stream().map(i -> i.getInfoHash().value()).collect(Collectors.toList()); + final List announcers = queue.drainAll().stream().map(i -> i.getInfoHash().value()).collect(toList()); assertThat(announcers) .hasSize(2) .containsExactly("one", "three"); @@ -69,11 +70,11 @@ public class DelayQueueTest { public void shouldBeThreadSafe() throws InterruptedException { final int announcerCount = 100; final DelayQueue queue = new DelayQueue<>(); - IntStream.range(0, announcerCount).forEach(i -> queue.addOrReplace(createInfoHashAble(String.valueOf(i)), -50, ChronoUnit.MILLIS)); + IntStream.range(0, announcerCount).forEach(i -> queue.addOrReplace(createInfoHashAble(valueOf(i)), -50, ChronoUnit.MILLIS)); final List>> callables = IntStream.range(0, 5) .mapToObj(i -> (Callable>) queue::getAvailables) - .collect(Collectors.toList()); + .collect(toList()); final ExecutorService executor = Executors.newFixedThreadPool(7); final List>> futures = executor.invokeAll(callables); final List results = futures.stream() @@ -85,7 +86,7 @@ public class DelayQueueTest { } }) .flatMap(List::stream) - .collect(Collectors.toList()); + .collect(toList()); assertThat(results).hasSize(announcerCount); } diff --git a/src/test/java/org/araymond/joal/web/config/JacksonConfigWebAppTest.java b/src/test/java/org/araymond/joal/web/config/JacksonConfigWebAppTest.java index 5b9c617..f4952a3 100644 --- a/src/test/java/org/araymond/joal/web/config/JacksonConfigWebAppTest.java +++ b/src/test/java/org/araymond/joal/web/config/JacksonConfigWebAppTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.araymond.joal.core.torrent.torrent.InfoHashTest; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; import javax.inject.Inject; import java.time.LocalDate; diff --git a/src/test/java/org/araymond/joal/web/resources/WebSocketControllerTest.java b/src/test/java/org/araymond/joal/web/resources/WebSocketControllerTest.java index 217e570..5e35512 100644 --- a/src/test/java/org/araymond/joal/web/resources/WebSocketControllerTest.java +++ b/src/test/java/org/araymond/joal/web/resources/WebSocketControllerTest.java @@ -129,7 +129,7 @@ public class WebSocketControllerTest { doReturn(true).when(seedManager).isSeeding(); doReturn("utorrent").when(seedManager).getCurrentEmulatedClient(); doReturn(Maps.newHashMap(InfoHashTest.createOne("abc"), mock(Speed.class))).when(seedManager).getSpeedMap(); - doReturn(Lists.newArrayList(announcerFacade)).when(seedManager).getCurrentlySeedingAnnouncer(); + doReturn(Lists.newArrayList(announcerFacade)).when(seedManager).getCurrentlySeedingAnnouncers(); controller.list(); @@ -139,7 +139,7 @@ public class WebSocketControllerTest { verify(seedManager, times(1)).isSeeding(); verify(seedManager, times(1)).getCurrentEmulatedClient(); verify(seedManager, times(1)).getSpeedMap(); - verify(seedManager, times(1)).getCurrentlySeedingAnnouncer(); + verify(seedManager, times(1)).getCurrentlySeedingAnnouncers(); verifyNoMoreInteractions(seedManager); } @@ -159,7 +159,7 @@ public class WebSocketControllerTest { doReturn(Lists.newArrayList(MockedTorrentTest.createOneMock("abc"), MockedTorrentTest.createOneMock("def"))).when(seedManager).getTorrentFiles(); doReturn(false).when(seedManager).isSeeding(); doReturn(Maps.newHashMap(InfoHashTest.createOne("abc"), mock(Speed.class))).when(seedManager).getSpeedMap(); - doReturn(Lists.newArrayList(announcerFacade)).when(seedManager).getCurrentlySeedingAnnouncer(); + doReturn(Lists.newArrayList(announcerFacade)).when(seedManager).getCurrentlySeedingAnnouncers(); controller.list(); @@ -168,7 +168,7 @@ public class WebSocketControllerTest { verify(seedManager, times(1)).getTorrentFiles(); verify(seedManager, times(1)).isSeeding(); verify(seedManager, times(1)).getSpeedMap(); - verify(seedManager, times(1)).getCurrentlySeedingAnnouncer(); + verify(seedManager, times(1)).getCurrentlySeedingAnnouncers(); verifyNoMoreInteractions(seedManager); } }