Cleanup v2 (#172)

* more cleanup
- add some clarifying comments;
- TooManyAnnouncesFailedInARowException -> TooManyAnnouncesFailedInARawException;
- pre-compile regex Patterns where makes sense; mainly in BitTorrentClient;
- simplify regexes;
- ClientNotifier: remove setClient() method, provide Client instance only via constructor;
- prefer primitives over boxed types, eg in
  - NumwantProvider
  - BitTorrentClientConfig
  - AppConfiguration
  - SeedingSpeedHasChangedPayload.SpeedPayload
  - DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm
  - KeyAlgorithm
  - HashNoLeadingZeroKeyAlgorithm
  - TimedOrAfterStartedAnnounceRefreshKeyGenerator
  - TimedRefreshKeyGenerator
  - RandomPoolWithChecksumPeerIdAlgorithm
  - TimedRefreshPeerIdGenerator
- change AnnouncerExecutor.currentlyRunning to ConcurrentHashMap; correct me if i'm wrong
  but looks like this is needed;
- move http-based tracker URI filtering logic from TrackerClient.announce() to
  TrackerClientUriProvider constructor, and throw if no http trackers are listed;
- add couple of additional IP providers to ConnectionHandler;
- SeedManager: add RequestConfig to the HttpClients builder, defining timeouts;
This commit is contained in:
laur89 2023-02-07 15:54:08 +01:00 committed by GitHub
parent 589895610d
commit ee3fd74ec9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 1217 additions and 1200 deletions

View file

@ -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"]

View file

@ -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

View file

@ -28,10 +28,10 @@ public class ApplicationClosingListener implements ApplicationListener<ContextCl
@Override
public void onApplicationEvent(final ContextClosedEvent event) {
log.info("Gracefully shutting down application.");
log.info("Gracefully shutting down application...");
manager.stop();
manager.tearDown();
log.info("JOAL gracefully shut down.");
log.info("JOAL gracefully shut down");
// Since we disabled log4j2 shutdown hook, we need to handle it manually.
final LifeCycle loggerContext = (LoggerContext) LogManager.getContext(false);

View file

@ -10,10 +10,8 @@ import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* Intercept core event, method can be @Async. THey MUST NOT interact with JOAL state, otherwise this class
* Intercept core event, method can be @Async. They MUST NOT interact with JOAL state, otherwise this class
* will soon turn into a god damn mess and we won't be able to maintain the code because of all the non explicit method calls.
*/
@Component
@ -22,15 +20,15 @@ public class CoreEventListener {
@Async
@Order(Ordered.HIGHEST_PRECEDENCE)
@EventListener
public void handleTorrentFileAddedForSeed(final TorrentFileAddedEvent event) throws IOException {
log.debug("Event TorrentFileAddedEvent caught.");
public void handleTorrentFileAddedForSeed(final TorrentFileAddedEvent event) {
log.debug("Event TorrentFileAddedEvent caught");
}
@Async
@Order(Ordered.HIGHEST_PRECEDENCE)
@EventListener
void handleSeedSessionHasStarted(final GlobalSeedStartedEvent event) {
log.debug("Event GlobalSeedStartedEvent caught.");
log.debug("Event GlobalSeedStartedEvent caught");
// TODO : add a log to tell which BitTorrent client.
// TODO : detailed BitTorrent client log at debug log level
}
@ -39,7 +37,7 @@ public class CoreEventListener {
@Order(Ordered.HIGHEST_PRECEDENCE)
@EventListener
public void handleSeedSessionHasEnded(final GlobalSeedStoppedEvent event) {
log.debug("Event GlobalSeedStoppedEvent caught.");
log.debug("Event GlobalSeedStoppedEvent caught");
// TODO : log that the seed session is over
}

View file

@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.SocketConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
@ -41,6 +42,12 @@ import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static java.nio.file.Files.isDirectory;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static org.apache.commons.lang3.ObjectUtils.firstNonNull;
import static org.springframework.http.HttpHeaders.USER_AGENT;
/**
* This is the outer boundary of our the business logic. Most (if not all)
* torrent-related handling is happening here & downstream.
@ -49,24 +56,23 @@ import java.util.concurrent.TimeUnit;
public class SeedManager {
private final CloseableHttpClient httpClient;
@Getter
private boolean seeding;
@Getter private boolean seeding;
private final JoalFoldersPath joalFoldersPath;
private final JoalConfigProvider configProvider;
private final TorrentFileProvider torrentFileProvider;
private final BitTorrentClientProvider bitTorrentClientProvider;
private final ApplicationEventPublisher publisher;
private final ConnectionHandler connectionHandler;
private final ApplicationEventPublisher appEventPublisher;
private final ConnectionHandler connectionHandler = new ConnectionHandler();
private BandwidthDispatcher bandwidthDispatcher;
private ClientFacade client;
public SeedManager(final String joalConfFolder, final ObjectMapper mapper, final ApplicationEventPublisher publisher) throws IOException {
this.joalFoldersPath = new JoalFoldersPath(Paths.get(joalConfFolder));
public SeedManager(final String joalConfRootPath, final ObjectMapper mapper,
final ApplicationEventPublisher appEventPublisher) throws IOException {
this.joalFoldersPath = new JoalFoldersPath(Paths.get(joalConfRootPath));
this.torrentFileProvider = new TorrentFileProvider(joalFoldersPath);
this.configProvider = new JoalConfigProvider(mapper, joalFoldersPath, publisher);
this.configProvider = new JoalConfigProvider(mapper, joalFoldersPath, appEventPublisher);
this.bitTorrentClientProvider = new BitTorrentClientProvider(configProvider, mapper, joalFoldersPath);
this.publisher = publisher;
this.connectionHandler = new ConnectionHandler();
this.appEventPublisher = appEventPublisher;
final SocketConfig sc = SocketConfig.custom()
.setSoTimeout(30_000)
@ -76,10 +82,18 @@ public class SeedManager {
connManager.setMaxTotal(200);
connManager.setValidateAfterInactivity(1000);
connManager.setDefaultSocketConfig(sc);
RequestConfig requestConf = RequestConfig.custom()
.setConnectTimeout(10_000)
.setConnectionRequestTimeout(5000) // timeout for requesting connection from connection manager
.setSocketTimeout(5000)
.build();
this.httpClient = HttpClients.custom()
.setConnectionTimeToLive(1, TimeUnit.MINUTES)
.setConnectionManager(connManager)
.setConnectionManagerShared(true)
.setDefaultRequestConfig(requestConf)
.build();
}
@ -93,40 +107,38 @@ public class SeedManager {
this.torrentFileProvider.stop();
if (this.client != null) {
this.client.stop();
this.client = null;
}
}
public void startSeeding() throws IOException {
if (this.client != null) {
if (this.seeding) {
log.warn("startSeeding() called, but already running");
return;
}
this.seeding = true;
this.configProvider.init();
final AppConfiguration appConfiguration = this.configProvider.get();
final List<String> 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<AnnouncerFacade> getCurrentlySeedingAnnouncer() {
return this.client == null ? new ArrayList<>() : client.getCurrentlySeedingAnnouncer();
public List<AnnouncerFacade> getCurrentlySeedingAnnouncers() {
return this.client == null ? emptyList() : client.getCurrentlySeedingAnnouncers();
}
public Map<InfoHash, Speed> 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<InfoHash, Speed> speeds) {
this.publisher.publishEvent(new SeedingSpeedsHasChangedEvent(speeds));
this.appEventPublisher.publishEvent(new SeedingSpeedsHasChangedEvent(speeds));
}
}
}

View file

@ -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:
* <ul>
* <li>listens for invocations from {@link BandwidthDispatcherNotifier} in order to register new torrents
* and update speeds based on the information received from tracker announce responses;</li>
* <li>periodically recomputes per-torrent speeds, and updates the tally in corresponding {@link TorrentSeedStats}</li>
* </ul>
*/
@Slf4j
@RequiredArgsConstructor
public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable {
private final ReentrantReadWriteLock lock;
private final WeightHolder<InfoHash> weightHolder;
private final RandomSpeedProvider randomSpeedProvider;
private final Map<InfoHash, TorrentSeedStats> torrentsSeedStats;
private final Map<InfoHash, Speed> speedMap;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final WeightHolder<InfoHash> weightHolder = new WeightHolder<>(new PeersAwareWeightCalculator());
private final Map<InfoHash, TorrentSeedStats> torrentsSeedStats = new HashMap<>();
private final Map<InfoHash, Speed> 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<Map.Entry<InfoHash, TorrentSeedStats>> entrySet = new HashSet<>(this.torrentsSeedStats.entrySet());
final Set<Map.Entry<InfoHash, TorrentSeedStats>> seedStatsView = new HashSet<>(this.torrentsSeedStats.entrySet());
this.lock.readLock().unlock();
for (final Map.Entry<InfoHash, TorrentSeedStats> 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());
}
}
}

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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 <a href="https://wiki.theory.org/BitTorrent_Tracker_Protocol">BitTorrent Tracker Protocol</a>
*/
@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) {

View file

@ -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();

View file

@ -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<E> {
private final Lock lock;
private final Lock lock = new ReentrantLock();
private final Map<E, Double> weightMap = new HashMap<>();
private final PeersAwareWeightCalculator weightCalculator;
private final Map<E, Double> 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<E> {
* 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);
}
}

View file

@ -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').
* <p/>
* It's responsible for composing/formatting the request data sent to the trackers. It
* makes no requests itself.
* <p/>
* 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<Map.Entry<String, String>> headers;
@Getter private final Set<Map.Entry<String, String>> headers;
private final NumwantProvider numwantProvider;
BitTorrentClient(final PeerIdGenerator peerIdGenerator, final KeyGenerator keyGenerator, final UrlEncoder urlEncoder, final String query, final Collection<HttpHeader> 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<HttpHeader> 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<Map.Entry<String, String>> 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<Map.Entry<String, String>> createRequestHeaders() {
final List<Map.Entry<String, String>> headers = new ArrayList<>(this.headers.size() + 1);
private Set<Map.Entry<String, String>> createRequestHeaders(Collection<HttpHeader> 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<String, String> 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());
}
}

View file

@ -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<HttpHeader> 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<HttpHeader> 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;

View file

@ -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}.
*
* <p/>
* Created by raymo on 23/04/2017.
*/
@Slf4j
@ -32,20 +36,23 @@ public class BitTorrentClientProvider implements Provider<BitTorrentClient> {
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<String> listClientFiles() {
try (final Stream<Path> paths = Files.walk(this.clientsFolderPath)) {
return paths.filter(p -> p.toString().endsWith(".client"))
try (final Stream<Path> 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<BitTorrentClient> {
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<BitTorrentClient> {
static final class SemanticVersionFilenameComparator implements Comparator<String> {
@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<BitTorrentClient> {
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;
}

View file

@ -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);
}
/**

View file

@ -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());
}
}

View file

@ -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,

View file

@ -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
) {

View file

@ -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;
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -12,6 +12,7 @@ import org.araymond.joal.core.torrent.torrent.InfoHash;
public class NeverRefreshPeerIdGenerator extends PeerIdGenerator {
private final String peerId;
@JsonCreator
NeverRefreshPeerIdGenerator(

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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);

View file

@ -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);
}

View file

@ -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");
}
}
}

View file

@ -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.
* <p/>
* Created by raymo on 18/04/2017.
*/
@Slf4j
public class JoalConfigProvider implements Provider<AppConfiguration> {
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<AppConfiguration> {
@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<AppConfiguration> {
}
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);

View file

@ -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;
}
}

View file

@ -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;
/**

View file

@ -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() {

View file

@ -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.
* <p>
@ -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);
}
}

View file

@ -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.
* <p/>
* 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.
* <p/>
* Created by raymo on 28/01/2017.
*/
@Slf4j
public class TorrentFileProvider extends FileAlterationListenerAdaptor {
private final TorrentFileWatcher watcher;
private final Map<File, MockedTorrent> torrentFiles = Collections.synchronizedMap(new HashMap<>());
private final Set<TorrentFileChangeAware> torrentFileChangeListener;
private final Map<File, MockedTorrent> torrentFiles = synchronizedMap(new HashMap<>());
private final Set<TorrentFileChangeAware> 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<InfoHash> unwantedTorrents) throws NoMoreTorrentsFileAvailableException {
Preconditions.checkNotNull(unwantedTorrents, "List of unwantedTorrents cannot be null.");
public MockedTorrent getTorrentNotIn(final Collection<InfoHash> 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));
}

View file

@ -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}.
* <p/>
* 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);

View file

@ -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
* <ul>
* <li>keeps track of & manages all the queued {@link AnnounceRequest}s via {@link DelayQueue}</li>
* <li>spawns a thread periodically going through the {@code delayQueue}, and generating
* tracker announcements off it</li>
* <li>implements {@link TorrentFileChangeAware} to react to torrent file changes in filesystem</li>
* </ul>
*/
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<Announcer> currentlySeedingAnnouncer;
private final DelayQueue<AnnounceRequest> delayQueue;
private final AnnouncerFactory announcerFactory;
private final ReentrantReadWriteLock lock;
private final List<Announcer> 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<AnnounceRequest> 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<AnnounceRequest> 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<AnnouncerFacade> getCurrentlySeedingAnnouncer() {
public List<AnnouncerFacade> 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();
}
}
}

View file

@ -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<AnnounceRequest> 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;
}
}

View file

@ -7,5 +7,5 @@ import java.util.List;
public interface ClientFacade {
void start();
void stop();
List<AnnouncerFacade> getCurrentlySeedingAnnouncer();
List<AnnouncerFacade> getCurrentlySeedingAnnouncers();
}

View file

@ -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:
* <ul>
* <li>establishes a socket on an open port that accepts connections</li>
* <li>periodically resolves our external IP address</li>
* </ul>
* Note this port & IP will be reported back to trackers via announcements, if your client
* {@code query} contains relevant placeholder(s).
* <p/>
* 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<InetAddress> tryToFetchFromProviders() {
final List<String> shuffledList = Lists.newArrayList(IP_PROVIDERS);
final List<String> 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<InetAddress> 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");
}
}

View file

@ -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<T extends DelayQueue.InfoHashAble> {
private final Lock lock = new ReentrantLock();
@ -28,26 +31,29 @@ public class DelayQueue<T extends DelayQueue.InfoHashAble> {
);
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<T> getAvailables() {
this.lock.lock();
try {
final IntervalAware<T> first = queue.peek();
final LocalDateTime now = LocalDateTime.now();
if (first == null || first.releaseAt.isAfter(now)) {
return Collections.emptyList();
return emptyList();
}
final List<T> 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<T extends DelayQueue.InfoHashAble> {
}
}
private Predicate<IntervalAware<T>> 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<T extends DelayQueue.InfoHashAble> {
this.lock.lock();
try {
final List<T> 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<T extends DelayQueue.InfoHashAble> {
}
}
@RequiredArgsConstructor
private static final class IntervalAware<T> implements Comparable<IntervalAware> {
@Getter
private final T item;
@Getter private final T item;
private final LocalDateTime releaseAt;
@Override

View file

@ -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<URI> 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<Integer> getLastKnownLeechers() {
return Optional.ofNullable(lastKnownLeechers);
return ofNullable(lastKnownLeechers);
}
@Override
public Optional<Integer> getLastKnownSeeders() {
return Optional.ofNullable(lastKnownSeeders);
return ofNullable(lastKnownSeeders);
}
@Override
public Optional<LocalDateTime> 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();

View file

@ -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;
}

View file

@ -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<Map.Entry<String, String>> getHttpHeadersForTorrent() {
return this.bitTorrentClient.createRequestHeaders();
public Set<Map.Entry<String, String>> getHttpHeadersForTorrent() {
return this.bitTorrentClient.getHeaders();
}
}

View file

@ -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();

View file

@ -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<Void> 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<Void> 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<Announcer> 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<Announcer> denyAll() {
final Set<InfoHash> infoHashes = new HashSet<>(this.currentlyRunning.keySet());
final List<Announcer> 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<Announcer> 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);
}
}

View file

@ -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));
}
}

View file

@ -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<AnnounceRequest> 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
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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<AnnounceResponseHandlerChainElement> chainElements;
private final List<AnnounceResponseHandler> 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));
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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<TrackerMessage> trackerResponseHandler;
public TrackerClient(final TrackerClientUriProvider trackerClientUriProvider, final ResponseHandler<TrackerMessage> 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<Map.Entry<String, String>> 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<Map.Entry<String, String>> headers) throws AnnounceException {
final String base = announceUri + (announceUri.toString().contains("?") ? "&": "?");
TrackerMessage makeCallAndGetResponseAsByteBuffer(final URI announceUri, final String requestQuery,
final Iterable<Map.Entry<String, String>> 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<String, String> 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 {

View file

@ -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<URI> addressIterator;
private URI currentURI = null;
@SneakyThrows
public TrackerClientUriProvider(@SuppressWarnings("TypeMayBeWeakened") final List<URI> trackersURI) {
List<URI> 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();
}

View file

@ -25,7 +25,7 @@ public class TrackerResponseHandler implements ResponseHandler<TrackerMessage> {
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 {

View file

@ -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);
}
}

View file

@ -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(),

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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<StompMessage> 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;
}

View file

@ -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));
}

View file

@ -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());
}

View file

@ -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);
}

View file

@ -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));
}

View file

@ -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

View file

@ -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<HttpHeader> 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<HttpHeader> 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<HttpHeader> 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<HttpHeader> 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}")

View file

@ -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);
}
}

View file

@ -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");
}

View file

@ -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();
}
}

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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) {

View file

@ -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();
}
}

View file

@ -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

View file

@ -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"));
}
}

View file

@ -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");
}

View file

@ -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<List<URI>> 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<List<URI>> 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();

View file

@ -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();

View file

@ -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))

View file

@ -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<AnnounceRequest> 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<AnnounceRequest>() {
@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<AnnounceRequest>() {
@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<AnnounceRequest> 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<AnnounceRequest> 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<AnnounceRequest> 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<AnnounceRequest> delayQueue = mock(DelayQueue.class);
final AnnouncerFactory mockedAnnouncerFactory = createMockedAnnouncerFactory();

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -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) {
}
}

View file

@ -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<TooManyAnnouncesFailedEvent> captor = ArgumentCaptor.forClass(TooManyAnnouncesFailedEvent.class);

View file

@ -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);
}

View file

@ -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) {
}
}

View file

@ -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);

View file

@ -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);

Some files were not shown because too many files have changed in this diff Show more