Merge pull request #170 from laur89/master

style changes
This commit is contained in:
Anthony Raymond 2022-11-02 20:25:39 +01:00 committed by GitHub
commit eb5d35c8ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
129 changed files with 790 additions and 1593 deletions

View file

@ -48,7 +48,7 @@ By default the web-ui is disabled, you can enable it with some more arguments:
- `--joal.ui.secret-token="SECRET_TOKEN"`: use your own secret token here (this is some kind of a password, choose a complicated one).
Once joal is started head to: `http://localhost:port/SECRET_OBFUSCATION_PATH/ui/` (obviously, replace `SECRET_OBFUSCATION_PATH`) by the value you had chosen
The `joal.ui.path.prefix` might seems useless but it's actually **crucial** to set it as complex as possible to prevent peoples to know that joal is running on your server.
The `joal.ui.path.prefix` might seems useless but it's actually **crucial** to set it as complex as possible to prevent people to know that joal is running on your server.
If you want to use iframe you may also pass the `joal.iframe.enabled=true` argument. If you don't known what that is just ignore it.
@ -135,7 +135,7 @@ Those projects are maintained by their individual authors, if you have any quest
# Thanks:
This project use a modified version of the awesome [mpetazzoni/ttorrent](http://mpetazzoni.github.com/ttorrent/) library. Thanks to **mpetazzoni** for this.
Also this project has benefited from the help of several peoples, see [Thanks.md](THANKS.md)
Also this project has benefited from the help of several people, see [Thanks.md](THANKS.md)
## Supporters
[![Thanks for providing Jetbrain license](readme-assets/jetbrains.svg)](https://www.jetbrains.com/?from=joal)

23
pom.xml
View file

@ -21,7 +21,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
@ -32,10 +32,9 @@
<java.version>11</java.version>
<!-- COMPILE -->
<log4j2.version>2.17.0</log4j2.version> <!-- TODO:Remove me after upgrading to Spring boot 2.7.0, This is to fix CVE-2021-44228, CVE-2021-45046 and CVE-2021-45105 -->
<commons-io.version>2.11.0</commons-io.version>
<generex.version>1.0.2</generex.version>
<google.guava.version>30.1.1-jre</google.guava.version>
<google.guava.version>31.1-jre</google.guava.version>
<ttorrent-core.version>1.5</ttorrent-core.version>
</properties>
@ -58,20 +57,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>-->
<exclusion>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
@ -120,17 +111,18 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<!--<version>${commons-lang3.version}</version> inherit version from parent -->
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<!--<version>${commons-codec.version}</version> inherit version from parent -->
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
<!--<version>${httpclient.version}</version> inherit version from parent -->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
@ -148,13 +140,11 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<!-- Version inherited from spring-boot-starter-test -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<!--<version>${assertj.version}</version> inherit version from parent -->
<scope>test</scope>
</dependency>
</dependencies>
@ -164,7 +154,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<!--<version>${maven-compiler-plugin.version}</version> inherit version from parent -->
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>

View file

@ -1,11 +1,10 @@
package org.araymond.joal;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LifeCycle;
import org.apache.logging.log4j.core.LoggerContext;
import org.araymond.joal.core.SeedManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Profile;
import org.springframework.context.event.ContextClosedEvent;
@ -18,9 +17,8 @@ import javax.inject.Inject;
*/
@Profile("!test")
@Component
@Slf4j
public class ApplicationClosingListener implements ApplicationListener<ContextClosedEvent> {
private static final Logger logger = LoggerFactory.getLogger(ApplicationClosingListener.class);
private final SeedManager manager;
@Inject
@ -30,10 +28,10 @@ public class ApplicationClosingListener implements ApplicationListener<ContextCl
@Override
public void onApplicationEvent(final ContextClosedEvent event) {
logger.info("Gracefully shutting down application.");
log.info("Gracefully shutting down application.");
manager.stop();
manager.tearDown();
logger.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

@ -1,8 +1,7 @@
package org.araymond.joal;
import lombok.extern.slf4j.Slf4j;
import org.araymond.joal.core.SeedManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
@ -16,9 +15,8 @@ import javax.inject.Inject;
*/
@Profile("!test")
@Component
@Slf4j
public class ApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
private static final Logger logger = LoggerFactory.getLogger(ApplicationReadyListener.class);
private final SeedManager manager;
private final ConfigurableApplicationContext applicationContext;
@ -35,7 +33,7 @@ public class ApplicationReadyListener implements ApplicationListener<Application
manager.startSeeding();
} catch (final Throwable e) {
final IllegalStateException wrapped = new IllegalStateException("Fatal error encountered", e);
logger.error("Fatal error encountered", wrapped);
log.error("Fatal error encountered", wrapped);
applicationContext.close();
}
}

View file

@ -1,7 +1,6 @@
package org.araymond.joal;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
@ -15,5 +14,4 @@ public class JackOfAllTradesApplication {
public static void main(final String[] args) {
SpringApplication.run(JackOfAllTradesApplication.class, args);
}
}
}

View file

@ -1,10 +1,9 @@
package org.araymond.joal.core;
import lombok.extern.slf4j.Slf4j;
import org.araymond.joal.core.events.global.state.GlobalSeedStartedEvent;
import org.araymond.joal.core.events.global.state.GlobalSeedStoppedEvent;
import org.araymond.joal.core.events.torrent.files.TorrentFileAddedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@ -18,25 +17,20 @@ import java.io.IOException;
* 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
@Slf4j
public class CoreEventListener {
private static final Logger logger = LoggerFactory.getLogger(CoreEventListener.class);
public CoreEventListener() {
}
@Async
@Order(Ordered.HIGHEST_PRECEDENCE)
@EventListener
public void handleTorrentFileAddedForSeed(final TorrentFileAddedEvent event) throws IOException {
logger.debug("Event TorrentFileAddedEvent caught.");
log.debug("Event TorrentFileAddedEvent caught.");
}
@Async
@Order(Ordered.HIGHEST_PRECEDENCE)
@EventListener
void handleSeedSessionHasStarted(final GlobalSeedStartedEvent event) {
logger.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
}
@ -45,7 +39,7 @@ public class CoreEventListener {
@Order(Ordered.HIGHEST_PRECEDENCE)
@EventListener
public void handleSeedSessionHasEnded(final GlobalSeedStoppedEvent event) {
logger.debug("Event GlobalSeedStoppedEvent caught.");
log.debug("Event GlobalSeedStoppedEvent caught.");
// TODO : log that the seed session is over
}

View file

@ -1,7 +1,9 @@
package org.araymond.joal.core;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.config.SocketConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
@ -29,8 +31,6 @@ import org.araymond.joal.core.ttorrent.client.DelayQueue;
import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFacade;
import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFactory;
import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceDataAccessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import java.io.IOException;
@ -38,20 +38,19 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Created by raymo on 27/01/2017.
* This is the outer boundary of our the business logic. Most (if not all)
* torrent-related handling is happening here & downstream.
*/
@Slf4j
public class SeedManager {
private static final Logger logger = LoggerFactory.getLogger(SeedManager.class);
private final CloseableHttpClient httpClient;
private boolean isSeeding;
@Getter
private boolean seeding;
private final JoalFoldersPath joalFoldersPath;
private final JoalConfigProvider configProvider;
private final TorrentFileProvider torrentFileProvider;
@ -61,6 +60,29 @@ public class SeedManager {
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));
this.torrentFileProvider = new TorrentFileProvider(joalFoldersPath);
this.configProvider = new JoalConfigProvider(mapper, joalFoldersPath, publisher);
this.bitTorrentClientProvider = new BitTorrentClientProvider(configProvider, mapper, joalFoldersPath);
this.publisher = publisher;
this.connectionHandler = new ConnectionHandler();
final SocketConfig sc = SocketConfig.custom()
.setSoTimeout(30_000)
.build();
final PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
connManager.setDefaultMaxPerRoute(100);
connManager.setMaxTotal(200);
connManager.setValidateAfterInactivity(1000);
connManager.setDefaultSocketConfig(sc);
this.httpClient = HttpClients.custom()
.setConnectionTimeToLive(1, TimeUnit.MINUTES)
.setConnectionManager(connManager)
.setConnectionManagerShared(true)
.build();
}
public void init() throws IOException {
this.connectionHandler.start();
this.torrentFileProvider.start();
@ -74,36 +96,11 @@ public class SeedManager {
}
}
public SeedManager(final String joalConfFolder, final ObjectMapper mapper, final ApplicationEventPublisher publisher) throws IOException {
this.isSeeding = false;
this.joalFoldersPath = new JoalFoldersPath(Paths.get(joalConfFolder));
this.torrentFileProvider = new TorrentFileProvider(joalFoldersPath);
this.configProvider = new JoalConfigProvider(mapper, joalFoldersPath, publisher);
this.bitTorrentClientProvider = new BitTorrentClientProvider(configProvider, mapper, joalFoldersPath);
this.publisher = publisher;
this.connectionHandler = new ConnectionHandler();
final SocketConfig sc = SocketConfig.custom()
.setSoTimeout(30000)
.build();
final PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
connManager.setDefaultMaxPerRoute(100);
connManager.setMaxTotal(200);
connManager.setValidateAfterInactivity(1000);
connManager.setDefaultSocketConfig(sc);
this.httpClient = HttpClients.custom()
.setConnectionTimeToLive(1, TimeUnit.MINUTES)
.setConnectionManager(connManager)
.setConnectionManagerShared(true)
.build();
}
public void startSeeding() throws IOException {
if (this.client != null) {
return;
}
this.isSeeding = true;
this.seeding = true;
this.configProvider.init();
final AppConfiguration appConfiguration = this.configProvider.get();
@ -144,7 +141,7 @@ public class SeedManager {
final String torrentName = name.endsWith(".torrent") ? name : name + ".torrent";
Files.write(this.joalFoldersPath.getTorrentFilesPath().resolve(torrentName), bytes, StandardOpenOption.CREATE);
} catch (final Exception e) {
logger.warn("Failed to save torrent file", 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));
@ -155,10 +152,6 @@ public class SeedManager {
this.torrentFileProvider.moveToArchiveFolder(torrentInfoHash);
}
public boolean isSeeding() {
return this.isSeeding;
}
public List<MockedTorrent> getTorrentFiles() {
return torrentFileProvider.getTorrentFiles();
}
@ -168,17 +161,11 @@ public class SeedManager {
}
public List<AnnouncerFacade> getCurrentlySeedingAnnouncer() {
if (this.client == null) {
return new ArrayList<>();
}
return client.getCurrentlySeedingAnnouncer();
return this.client == null ? new ArrayList<>() : client.getCurrentlySeedingAnnouncer();
}
public Map<InfoHash, Speed> getSpeedMap() {
if (this.bandwidthDispatcher == null) {
return Maps.newHashMap();
}
return bandwidthDispatcher.getSpeedMap();
return this.bandwidthDispatcher == null ? new HashMap<>() : bandwidthDispatcher.getSpeedMap();
}
public AppConfiguration getCurrentConfig() {
@ -203,7 +190,7 @@ public class SeedManager {
}
public void stop() {
this.isSeeding = false;
this.seeding = false;
if (client != null) {
this.client.stop();
this.publisher.publishEvent(new GlobalSeedStoppedEvent());
@ -217,54 +204,41 @@ public class SeedManager {
}
@Getter
public static class JoalFoldersPath {
private final Path confPath;
private final Path torrentFilesPath;
private final Path torrentArchivedPath;
private final Path clientsFilesPath;
/**
* 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");
if (!Files.exists(confPath)) {
logger.warn("No such directory: {}", this.confPath.toString());
if (!Files.isDirectory(confPath)) {
log.warn("No such directory: {}", this.confPath);
}
if (!Files.exists(torrentFilesPath)) {
logger.warn("Sub-folder 'torrents' is missing in joal conf folder: {}", this.torrentFilesPath.toString());
if (!Files.isDirectory(torrentFilesPath)) {
log.warn("Sub-folder 'torrents' is missing in joal conf folder: {}", this.torrentFilesPath);
}
if (!Files.exists(clientsFilesPath)) {
logger.warn("Sub-folder 'clients' is missing in joal conf folder: {}", this.clientsFilesPath.toString());
if (!Files.isDirectory(clientsFilesPath)) {
log.warn("Sub-folder 'clients' is missing in joal conf folder: {}", this.clientsFilesPath);
}
}
public Path getConfPath() {
return confPath;
}
public Path getTorrentFilesPath() {
return torrentFilesPath;
}
public Path getTorrentArchivedPath() {
return torrentArchivedPath;
}
public Path getClientsFilesPath() {
return clientsFilesPath;
}
}
@RequiredArgsConstructor
private static final class SeedManagerSpeedChangeListener implements SpeedChangedListener {
private final ApplicationEventPublisher publisher;
private SeedManagerSpeedChangeListener(final ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public void speedsHasChanged(final Map<InfoHash, Speed> speeds) {
this.publisher.publishEvent(new SeedingSpeedsHasChangedEvent(speeds));
}
}
}

View file

@ -1,41 +1,44 @@
package org.araymond.joal.core.bandwith;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
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.slf4j.Logger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.slf4j.LoggerFactory.getLogger;
import static java.util.Optional.ofNullable;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.apache.commons.lang3.ObjectUtils.getIfNull;
@Slf4j
public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable {
private static final Logger logger = getLogger(BandwidthDispatcher.class);
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 SpeedChangedListener speedChangedListener;
private final int threadPauseInterval;
private int threadLoopCounter = 0;
private volatile boolean stop = false;
private final int threadPauseIntervalMs;
private int threadLoopCounter;
private volatile boolean stop;
private Thread thread;
public BandwidthDispatcher(final int threadPauseInterval, final RandomSpeedProvider randomSpeedProvider) {
this.threadPauseInterval = threadPauseInterval;
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;
}
@ -44,19 +47,18 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable
this.speedChangedListener = speedListener;
}
/*
/**
* This method does not benefit from the lock, because the value will never be accessed in a ambiguous way.
* And even if it happens, we returns 0 by default.
* And even if it happens, we return 0 by default.
*/
public TorrentSeedStats getSeedStatForTorrent(final InfoHash infoHash) {
final TorrentSeedStats torrentSeedStats = this.torrentsSeedStats.get(infoHash);
return torrentSeedStats == null ? new TorrentSeedStats() : torrentSeedStats;
return getIfNull(this.torrentsSeedStats.get(infoHash), TorrentSeedStats::new);
}
public Map<InfoHash, Speed> getSpeedMap() {
try {
this.lock.readLock().lock();
return Maps.newHashMap(speedMap);
return new HashMap<>(speedMap);
} finally {
this.lock.readLock().unlock();
}
@ -82,27 +84,29 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable
public void run() {
try {
while (!this.stop) {
Thread.sleep(this.threadPauseInterval);
Thread.sleep(this.threadPauseIntervalMs);
++this.threadLoopCounter;
// refresh bandwidth every 1200000 milliseconds (20 minutes)
if (this.threadLoopCounter == 1200000 / this.threadPauseInterval) {
// refresh bandwidth every 20 minutes:
if (this.threadLoopCounter == TWENTY_MINS_MS / this.threadPauseIntervalMs) {
this.refreshCurrentBandwidth();
this.threadLoopCounter = 0;
}
// This method as to run as fast as possible to avoid blocking other ones. Because we wan't this loop
// to be scheduled as precise as we can. Locking to much will delay the Thread.sleep and cause stats
// This method as to run as fast as possible to avoid blocking other ones. Because we want this loop
// 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 = Sets.newHashSet(this.torrentsSeedStats.entrySet());
final Set<Map.Entry<InfoHash, TorrentSeedStats>> entrySet = new HashSet<>(this.torrentsSeedStats.entrySet());
this.lock.readLock().unlock();
for (final Map.Entry<InfoHash, TorrentSeedStats> entry : entrySet) {
final Speed speed = this.speedMap.get(entry.getKey());
final long speedInBytesPerSecond = speed == null ? 0: speed.getBytesPerSeconds(); // avoid Map#getOrDefault as it will trigger a lot of Speed object instantiation for nothing.
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.threadPauseInterval) / 1000);
entry.getValue().addUploaded((speedInBytesPerSecond * this.threadPauseIntervalMs) / 1000);
}
}
} catch (final InterruptedException ignore) {
@ -110,9 +114,7 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable
}
public void updateTorrentPeers(final InfoHash infoHash, final int seeders, final int leechers) {
if (logger.isDebugEnabled()) {
logger.debug("Updating Peers stats for {}", infoHash.humanReadableValue());
}
log.debug("Updating Peers stats for {}", infoHash.getHumanReadable());
this.lock.writeLock().lock();
try {
this.weightHolder.addOrUpdate(infoHash, new Peers(seeders, leechers));
@ -123,9 +125,7 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable
}
public void registerTorrent(final InfoHash infoHash) {
if (logger.isDebugEnabled()) {
logger.debug("{} has been added to bandwidth dispatcher.", infoHash.humanReadableValue());
}
log.debug("{} has been added to bandwidth dispatcher.", infoHash.getHumanReadable());
this.lock.writeLock().lock();
try {
this.torrentsSeedStats.put(infoHash, new TorrentSeedStats());
@ -136,9 +136,7 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable
}
public void unregisterTorrent(final InfoHash infoHash) {
if (logger.isDebugEnabled()) {
logger.debug("{} has been removed from bandwidth dispatcher.", infoHash.humanReadableValue());
}
log.debug("{} has been removed from bandwidth dispatcher.", infoHash.getHumanReadable());
this.lock.writeLock().lock();
try {
this.weightHolder.remove(infoHash);
@ -152,15 +150,13 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable
@VisibleForTesting
void refreshCurrentBandwidth() {
if (logger.isDebugEnabled()) {
logger.debug("Refreshing global bandwidth");
}
log.debug("Refreshing global bandwidth");
this.lock.writeLock().lock();
try {
this.randomSpeedProvider.refresh();
this.recomputeSpeeds();
if (logger.isDebugEnabled()) {
logger.debug("Global bandwidth refreshed, new value is {}", FileUtils.byteCountToDisplaySize(this.randomSpeedProvider.getInBytesPerSeconds()));
if (log.isDebugEnabled()) {
log.debug("Global bandwidth refreshed, new value is {}", FileUtils.byteCountToDisplaySize(this.randomSpeedProvider.getCurrentSpeed()));
}
} finally {
this.lock.writeLock().unlock();
@ -169,37 +165,37 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable
@VisibleForTesting
void recomputeSpeeds() {
if (logger.isDebugEnabled()) {
logger.debug("Refreshing all torrents speeds");
}
log.debug("Refreshing all torrents speeds");
for (final InfoHash infohash : this.torrentsSeedStats.keySet()) {
final double percentOfSpeedAssigned = this.weightHolder.getTotalWeight() == 0.0
? 0.0
: this.weightHolder.getWeightFor(infohash) / this.weightHolder.getTotalWeight();
this.speedMap.compute(infohash, (hash, speed) -> {
if (speed == null) {
return new Speed(0);
}
speed.setBytesPerSeconds((long) (this.randomSpeedProvider.getInBytesPerSeconds() * percentOfSpeedAssigned));
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;
});
}
if (speedChangedListener != null) {
this.speedChangedListener.speedsHasChanged(Maps.newHashMap(this.speedMap));
this.speedChangedListener.speedsHasChanged(new HashMap<>(this.speedMap));
}
try {
if (logger.isDebugEnabled()) {
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.getBytesPerSeconds());
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.humanReadableValue())
.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()))
@ -207,10 +203,10 @@ public class BandwidthDispatcher implements BandwidthDispatcherFacade, Runnable
.append("\n");
});
sb.setLength(sb.length() - 1); // remove last \n
logger.debug(sb.toString());
log.debug(sb.toString());
}
} catch (final Exception e) {
logger.debug("Error while printing debug message for speed.", e);
log.debug("Error while printing debug message for speed.", e);
}
}

View file

@ -3,5 +3,5 @@ package org.araymond.joal.core.bandwith;
import org.araymond.joal.core.torrent.torrent.InfoHash;
public interface BandwidthDispatcherFacade {
TorrentSeedStats getSeedStatForTorrent(final InfoHash infoHash);
TorrentSeedStats getSeedStatForTorrent(InfoHash infoHash);
}

View file

@ -1,7 +1,10 @@
package org.araymond.joal.core.bandwith;
import com.google.common.base.Objects;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@EqualsAndHashCode(of = {"seeders", "leechers"})
@Getter
public class Peers {
private final int seeders;
private final int leechers;
@ -14,30 +17,4 @@ public class Peers {
? 0
: ((float) this.leechers) / (this.seeders + this.leechers);
}
public int getSeeders() {
return seeders;
}
public int getLeechers() {
return leechers;
}
public float getLeechersRatio() {
return leechersRatio;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Peers peers = (Peers) o;
return seeders == peers.seeders &&
leechers == peers.leechers;
}
@Override
public int hashCode() {
return Objects.hashCode(seeders, leechers);
}
}

View file

@ -1,29 +1,26 @@
package org.araymond.joal.core.bandwith;
import lombok.Getter;
import org.araymond.joal.core.config.AppConfiguration;
import java.util.concurrent.ThreadLocalRandom;
public class RandomSpeedProvider {
private final AppConfiguration appConfiguration;
private long currentSpeed;
@Getter
private long currentSpeed; // bytes/s
public RandomSpeedProvider(final AppConfiguration appConfiguration) {
this.appConfiguration = appConfiguration;
this.refresh();
}
public void refresh() {
final Long minUploadRateInBytes = appConfiguration.getMinUploadRate() * 1000L;
final Long maxUploadRateInBytes = appConfiguration.getMaxUploadRate() * 1000L;
this.currentSpeed = (minUploadRateInBytes.equals(maxUploadRateInBytes))
final long minUploadRateInBytes = appConfiguration.getMinUploadRate() * 1000L;
final long maxUploadRateInBytes = appConfiguration.getMaxUploadRate() * 1000L;
this.currentSpeed = (minUploadRateInBytes == maxUploadRateInBytes)
? maxUploadRateInBytes
: ThreadLocalRandom.current().nextLong(minUploadRateInBytes, maxUploadRateInBytes);
}
public long getInBytesPerSeconds() {
return this.currentSpeed;
}
}

View file

@ -1,17 +1,10 @@
package org.araymond.joal.core.bandwith;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Speed {
private long bytesPerSeconds;
Speed(final long bytesPerSeconds) {
this.bytesPerSeconds = bytesPerSeconds;
}
public long getBytesPerSeconds() {
return bytesPerSeconds;
}
void setBytesPerSeconds(final long bytesPerSeconds) {
this.bytesPerSeconds = bytesPerSeconds;
}
private long bytesPerSecond;
}

View file

@ -5,5 +5,5 @@ import org.araymond.joal.core.torrent.torrent.InfoHash;
import java.util.Map;
public interface SpeedChangedListener {
void speedsHasChanged(final Map<InfoHash, Speed> speeds);
void speedsHasChanged(Map<InfoHash, Speed> speeds);
}

View file

@ -1,22 +1,15 @@
package org.araymond.joal.core.bandwith;
import lombok.Getter;
@Getter
public class TorrentSeedStats {
private long uploaded;
private long downloaded;
private long left;
void addUploaded(final long bytes) {
this.uploaded += bytes;
}
public long getUploaded() {
return uploaded;
}
public long getDownloaded() {
return 0L;
}
public long getLeft() {
return 0L;
}
}

View file

@ -4,15 +4,9 @@ import org.araymond.joal.core.bandwith.Peers;
public class PeersAwareWeightCalculator {
public double calculate(final Peers peers) {
if (peers.getSeeders() == 0) {
return 0.0;
}
final double leechersRatio = peers.getLeechersRatio();
if (leechersRatio == 0) {
return 0.0;
}
return leechersRatio * 100
* (peers.getSeeders() * leechersRatio)
* (((double) peers.getLeechers()) / peers.getSeeders());
return (peers.getSeeders() == 0 || leechersRatio == 0)
? 0.0
: leechersRatio * 100 * leechersRatio * peers.getLeechers();
}
}

View file

@ -1,5 +1,6 @@
package org.araymond.joal.core.bandwith.weight;
import lombok.Getter;
import org.araymond.joal.core.bandwith.Peers;
import java.util.HashMap;
@ -7,11 +8,15 @@ import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static java.util.Optional.ofNullable;
public class WeightHolder<E> {
private final Lock lock;
private final PeersAwareWeightCalculator weightCalculator;
private final Map<E, Weight> weightMap;
private final Map<E, Double> weightMap;
@Getter
private double totalWeight;
public WeightHolder(final PeersAwareWeightCalculator weightCalculator) {
@ -24,13 +29,9 @@ public class WeightHolder<E> {
final double weight = this.weightCalculator.calculate(peers);
lock.lock();
try {
final Weight previousWeight = this.weightMap.put(item, new Weight(weight));
if (previousWeight != null) {
this.totalWeight = this.totalWeight - previousWeight.getWeight() + weight;
} else {
this.totalWeight += weight;
}
ofNullable(this.weightMap.put(item, weight)).ifPresentOrElse(
previousWeight -> this.totalWeight = this.totalWeight - previousWeight + weight,
() -> this.totalWeight += weight);
} finally {
lock.unlock();
}
@ -39,46 +40,21 @@ public class WeightHolder<E> {
public void remove(final E item) {
lock.lock();
try {
final Weight weight = this.weightMap.remove(item);
if (weight != null) {
this.totalWeight -= weight.getWeight();
}
ofNullable(this.weightMap.remove(item))
.ifPresent(w -> this.totalWeight -= w);
} finally {
lock.unlock();
}
}
/*
/**
* 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 il will returns the previous value.
*
* - if a value is still present it will return the previous value.
*/
public double getWeightFor(final E item) {
final Weight weight = this.weightMap.get(item);
if (weight == null) {
return 0.0;
}
return weight.getWeight();
}
public double getTotalWeight() {
return totalWeight;
}
/**
* Wrap double to prevent unboxing
*/
private static final class Weight {
private final double weight;
private Weight(final double weight) {
this.weight = weight;
}
double getWeight() {
return weight;
}
return ofNullable(weightMap.get(item))
.orElse(0.0);
}
}

View file

@ -1,10 +1,11 @@
package org.araymond.joal.core.client.emulated;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
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;
import org.apache.commons.lang3.StringUtils;
import org.araymond.joal.core.bandwith.TorrentSeedStats;
import org.araymond.joal.core.client.emulated.generator.UrlEncoder;
@ -22,16 +23,18 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.Optional.ofNullable;
import static org.araymond.joal.core.client.emulated.BitTorrentClientConfig.HttpHeader;
/**
* Created by raymo on 26/01/2017.
*/
@EqualsAndHashCode(exclude = "urlEncoder")
public class BitTorrentClient {
private final PeerIdGenerator peerIdGenerator;
private final KeyGenerator keyGenerator;
private final UrlEncoder urlEncoder;
private final String query;
@Getter private final String query;
private final List<Map.Entry<String, String>> headers;
private final NumwantProvider numwantProvider;
@ -55,14 +58,8 @@ public class BitTorrentClient {
@VisibleForTesting
Optional<String> getKey(final InfoHash infoHash, final RequestEvent event) {
if (keyGenerator == null) {
return Optional.empty();
}
return Optional.of(keyGenerator.getKey(infoHash, event));
}
public String getQuery() {
return query;
return ofNullable(keyGenerator)
.map(keyGen -> keyGen.getKey(infoHash, event));
}
public List<Map.Entry<String, String>> getHeaders() {
@ -83,12 +80,9 @@ public class BitTorrentClient {
.replaceAll("\\{port}", String.valueOf(connectionHandler.getPort()))
.replaceAll("\\{numwant}", String.valueOf(this.getNumwant(event)));
final String peerId;
if (this.peerIdGenerator.getShouldUrlEncoded()) {
peerId = urlEncoder.encode(this.getPeerId(torrentInfoHash, event));
} else {
peerId = this.getPeerId(torrentInfoHash, event);
}
final String peerId = this.peerIdGenerator.isShouldUrlEncode()
? urlEncoder.encode(this.getPeerId(torrentInfoHash, event))
: this.getPeerId(torrentInfoHash, event);
emulatedClientQuery = emulatedClientQuery.replaceAll("\\{peerid}", peerId);
// set ip or ipv6 then remove placeholders that were left empty
@ -149,22 +143,4 @@ public class BitTorrentClient {
}
return headers;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final BitTorrentClient that = (BitTorrentClient) o;
return com.google.common.base.Objects.equal(peerIdGenerator, that.peerIdGenerator) &&
Objects.equal(keyGenerator, that.keyGenerator) &&
Objects.equal(query, that.query) &&
Objects.equal(headers, that.headers) &&
Objects.equal(numwantProvider, that.numwantProvider);
}
@Override
public int hashCode() {
return Objects.hashCode(peerIdGenerator, keyGenerator, query, headers, numwantProvider);
}
}

View file

@ -2,12 +2,11 @@ package org.araymond.joal.core.client.emulated;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.araymond.joal.core.client.emulated.generator.UrlEncoder;
import org.araymond.joal.core.client.emulated.generator.key.KeyGenerator;
import org.araymond.joal.core.client.emulated.generator.numwant.NumwantProvider;
import org.araymond.joal.core.client.emulated.generator.peerid.PeerIdGenerator;
import java.util.List;
@ -15,6 +14,8 @@ import java.util.List;
/**
* Created by raymo on 24/01/2017.
*/
@EqualsAndHashCode
@Getter
public class BitTorrentClientConfig {
@JsonProperty("peerIdGenerator")
private final PeerIdGenerator peerIdGenerator;
@ -49,45 +50,13 @@ public class BitTorrentClientConfig {
this.numwant = numwant;
this.numwantOnStop = numwantOnStop;
if (this.query.contains("{key}")) {
if (this.keyGenerator == null) {
throw new TorrentClientConfigIntegrityException("Query string contains {key}, but no keyGenerator was found in .client file.");
}
if (this.query.contains("{key}") && this.keyGenerator == null) {
throw new TorrentClientConfigIntegrityException("Query string contains {key}, but no keyGenerator was found in .client file.");
}
}
@VisibleForTesting
public BitTorrentClient createClient() {
return new BitTorrentClient(
this.peerIdGenerator,
this.keyGenerator,
this.urlEncoder,
query,
ImmutableList.copyOf(requestHeaders),
new NumwantProvider(this.numwant, this.numwantOnStop)
);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final BitTorrentClientConfig that = (BitTorrentClientConfig) o;
return com.google.common.base.Objects.equal(peerIdGenerator, that.peerIdGenerator) &&
com.google.common.base.Objects.equal(urlEncoder, that.urlEncoder) &&
com.google.common.base.Objects.equal(query, that.query) &&
com.google.common.base.Objects.equal(keyGenerator, that.keyGenerator) &&
com.google.common.base.Objects.equal(requestHeaders, that.requestHeaders) &&
com.google.common.base.Objects.equal(numwant, that.numwant) &&
com.google.common.base.Objects.equal(numwantOnStop, that.numwantOnStop);
}
@Override
public int hashCode() {
return com.google.common.base.Objects.hashCode(peerIdGenerator, query, keyGenerator, urlEncoder, requestHeaders, numwant, numwantOnStop);
}
@EqualsAndHashCode
@Getter
public static class HttpHeader {
private final String name;
private final String value;
@ -99,28 +68,5 @@ public class BitTorrentClientConfig {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final HttpHeader header = (HttpHeader) o;
return com.google.common.base.Objects.equal(name, header.name) &&
com.google.common.base.Objects.equal(value, header.value);
}
@Override
public int hashCode() {
return com.google.common.base.Objects.hashCode(name, value);
}
}
}

View file

@ -1,11 +1,12 @@
package org.araymond.joal.core.client.emulated;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.araymond.joal.core.SeedManager;
import org.araymond.joal.core.client.emulated.generator.numwant.NumwantProvider;
import org.araymond.joal.core.config.JoalConfigProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Provider;
import java.io.FileNotFoundException;
@ -14,15 +15,18 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
/**
* Provides as with an instance of {@link BitTorrentClient}, based on the
* configured {@code client}.
*
* Created by raymo on 23/04/2017.
*/
@Slf4j
public class BitTorrentClientProvider implements Provider<BitTorrentClient> {
private static final Logger logger = LoggerFactory.getLogger(BitTorrentClientProvider.class);
private BitTorrentClient bitTorrentClient;
private final JoalConfigProvider configProvider;
private final ObjectMapper objectMapper;
@ -39,7 +43,7 @@ public class BitTorrentClientProvider implements Provider<BitTorrentClient> {
return paths.filter(p -> p.toString().endsWith(".client"))
.map(p -> p.getFileName().toString())
.sorted(new SemanticVersionFilenameComparator())
.collect(Collectors.toList());
.collect(toList());
} catch (final IOException e) {
throw new IllegalStateException("Failed to walk through .clients files", e);
}
@ -58,20 +62,30 @@ public class BitTorrentClientProvider implements Provider<BitTorrentClient> {
}
public void generateNewClient() throws FileNotFoundException, IllegalStateException {
logger.debug("Generating new client.");
final Path clientConfigPath = clientsFolderPath.resolve(configProvider.get().getClientFileName());
if (!Files.exists(clientConfigPath)) {
throw new FileNotFoundException(String.format("BitTorrent client configuration file '%s' not found.", clientConfigPath.toAbsolutePath()));
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()));
}
final BitTorrentClientConfig config;
try {
config = objectMapper.readValue(clientConfigPath.toFile(), BitTorrentClientConfig.class);
BitTorrentClientConfig config = objectMapper.readValue(clientConfigPath.toFile(), BitTorrentClientConfig.class);
this.bitTorrentClient = createClient(config);
log.debug("New client successfully generated");
} catch (final IOException e) {
throw new IllegalStateException(e);
}
this.bitTorrentClient = config.createClient();
logger.debug("New client successfully generated.");
}
public BitTorrentClient createClient(BitTorrentClientConfig clientConfig) {
return new BitTorrentClient(
clientConfig.getPeerIdGenerator(),
clientConfig.getKeyGenerator(),
clientConfig.getUrlEncoder(),
clientConfig.getQuery(),
ImmutableList.copyOf(clientConfig.getRequestHeaders()),
new NumwantProvider(clientConfig.getNumwant(), clientConfig.getNumwantOnStop())
);
}
static final class SemanticVersionFilenameComparator implements Comparator<String> {

View file

@ -6,7 +6,6 @@ package org.araymond.joal.core.client.emulated;
public class TorrentClientConfigIntegrityException extends RuntimeException {
private static final long serialVersionUID = -2441989395992766363L;
public TorrentClientConfigIntegrityException(final String message) {
super(message);
}

View file

@ -4,12 +4,17 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.araymond.joal.core.client.emulated.utils.Casing;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.String.format;
import static java.lang.String.valueOf;
@EqualsAndHashCode(of = {"encodingExclusionPattern", "encodedHexCase"})
@Getter
public class UrlEncoder {
@JsonProperty("encodingExclusionPattern")
@ -29,14 +34,6 @@ public class UrlEncoder {
this.encodedHexCase = encodedHexCase;
}
String getEncodingExclusionPattern() {
return encodingExclusionPattern;
}
Casing getEncodedHexCase() {
return encodedHexCase;
}
/**
* UrlEncode a string, it does NOT change the casing of the regular characters, but it lower all encoded characters
* @param toBeEncoded string to encode
@ -44,7 +41,7 @@ public class UrlEncoder {
*/
public String encode(final String toBeEncoded) {
final StringBuilder sb = new StringBuilder();
for(final char ch: toBeEncoded.toCharArray()) {
for (final char ch : toBeEncoded.toCharArray()) {
sb.append(this.urlEncodeChar(ch));
}
return sb.toString();
@ -52,32 +49,11 @@ public class UrlEncoder {
@VisibleForTesting
String urlEncodeChar(final char character) {
final Matcher matcher = pattern.matcher("" + character);
if (matcher.matches()) {
return "" + character;
}
final String hex;
if (character == 0) {
hex = "%00";
} else {
hex = String.format("%%%02x", (int) character);
if (pattern.matcher(valueOf(character)).matches()) {
return valueOf(character);
}
final String hex = character == 0 ? "%00" : format("%%%02x", (int) character);
return encodedHexCase.toCase(hex);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final UrlEncoder that = (UrlEncoder) o;
return Objects.equal(encodingExclusionPattern, that.encodingExclusionPattern) &&
encodedHexCase == that.encodedHexCase;
}
@Override
public int hashCode() {
return Objects.hashCode(encodingExclusionPattern, encodedHexCase);
}
}

View file

@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.EqualsAndHashCode;
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;
@ -22,9 +24,13 @@ import org.araymond.joal.core.torrent.torrent.InfoHash;
@JsonSubTypes.Type(value = TorrentVolatileRefreshKeyGenerator.class, name = "TORRENT_VOLATILE"),
@JsonSubTypes.Type(value = TorrentPersistentRefreshKeyGenerator.class, name = "TORRENT_PERSISTENT")
})
@Getter
@EqualsAndHashCode
public abstract class KeyGenerator {
@JsonProperty("algorithm")
private final KeyAlgorithm algorithm;
@JsonProperty("keyCase")
private final Casing keyCase;
protected KeyGenerator(final KeyAlgorithm keyAlgorithm, final Casing keyCase) {
@ -35,18 +41,6 @@ public abstract class KeyGenerator {
this.keyCase = keyCase;
}
@JsonProperty("algorithm")
KeyAlgorithm getAlgorithm() {
return algorithm;
}
@JsonProperty("keyCase")
Casing getKeyCase() {
return keyCase;
}
@JsonIgnore
public abstract String getKey(final InfoHash infoHash, RequestEvent event);
@ -56,18 +50,4 @@ public abstract class KeyGenerator {
return keyCase.toCase(key);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final KeyGenerator keyGenerator = (KeyGenerator) o;
return keyCase == keyGenerator.keyCase &&
com.google.common.base.Objects.equal(algorithm, keyGenerator.algorithm);
}
@Override
public int hashCode() {
return com.google.common.base.Objects.hashCode(algorithm, keyCase);
}
}

View file

@ -19,7 +19,6 @@ public class NeverRefreshKeyGenerator extends KeyGenerator {
@JsonProperty(value = "keyCase", required = true) final Casing keyCase
) {
super(algorithm, keyCase);
this.key = generateKey();
}
@ -27,5 +26,4 @@ public class NeverRefreshKeyGenerator extends KeyGenerator {
public String getKey(final InfoHash infoHash, final RequestEvent event) {
return key;
}
}

View file

@ -4,22 +4,18 @@ 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;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
/**
* Created by raymo on 16/07/2017.
*/
public class TimedOrAfterStartedAnnounceRefreshKeyGenerator extends KeyGenerator {
@VisibleForTesting
LocalDateTime lastGeneration;
private String key;
private final Integer refreshEvery;
public class TimedOrAfterStartedAnnounceRefreshKeyGenerator extends TimedRefreshKeyGenerator {
@JsonCreator
TimedOrAfterStartedAnnounceRefreshKeyGenerator(
@ -27,35 +23,22 @@ public class TimedOrAfterStartedAnnounceRefreshKeyGenerator extends KeyGenerator
@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.");
}
this.refreshEvery = refreshEvery;
}
@JsonProperty("refreshEvery")
Integer getRefreshEvery() {
return refreshEvery;
super(refreshEvery, algorithm, keyCase);
}
@Override
public String getKey(final InfoHash infoHash, final RequestEvent event) {
if (this.shouldRegenerateKey()) {
if (super.shouldRegenerateKey()) {
this.lastGeneration = LocalDateTime.now();
this.key = super.generateKey();
setKey(generateKey());
}
final String key = this.key;
final String key = getKey();
if (event == RequestEvent.STARTED) {
this.key = super.generateKey();
setKey(generateKey());
}
return key;
}
private boolean shouldRegenerateKey() {
return this.lastGeneration == null || ChronoUnit.SECONDS.between(this.lastGeneration, LocalDateTime.now()) >= this.refreshEvery;
}
}

View file

@ -1,9 +1,11 @@
package org.araymond.joal.core.client.emulated.generator.key;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
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;
@ -18,7 +20,13 @@ import java.time.temporal.ChronoUnit;
public class TimedRefreshKeyGenerator extends KeyGenerator {
@VisibleForTesting
LocalDateTime lastGeneration;
@Getter
@JsonIgnore
private String key;
@JsonProperty("refreshEvery")
@Getter
private final Integer refreshEvery;
@JsonCreator
@ -34,11 +42,6 @@ public class TimedRefreshKeyGenerator extends KeyGenerator {
this.refreshEvery = refreshEvery;
}
@JsonProperty("refreshEvery")
Integer getRefreshEvery() {
return refreshEvery;
}
@Override
public String getKey(final InfoHash infoHash, final RequestEvent event) {
if (this.shouldRegenerateKey()) {
@ -49,7 +52,11 @@ public class TimedRefreshKeyGenerator extends KeyGenerator {
return this.key;
}
private boolean shouldRegenerateKey() {
boolean shouldRegenerateKey() {
return this.lastGeneration == null || ChronoUnit.SECONDS.between(this.lastGeneration, LocalDateTime.now()) >= this.refreshEvery;
}
void setKey(String key) {
this.key = key;
}
}

View file

@ -3,14 +3,15 @@ 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.google.common.collect.Sets;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.Getter;
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;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -41,7 +42,7 @@ public class TorrentPersistentRefreshKeyGenerator extends KeyGenerator {
}
private void evictOldEntries() {
Sets.newHashSet(this.keyPerTorrent.entrySet()).stream()
new HashSet<>(this.keyPerTorrent.entrySet()).stream()
.filter(this::shouldEvictEntry)
.forEach(entry -> this.keyPerTorrent.remove(entry.getKey()));
}
@ -59,6 +60,7 @@ public class TorrentPersistentRefreshKeyGenerator extends KeyGenerator {
static class AccessAwareKey {
private final String peerId;
@Getter
private LocalDateTime lastAccess;
@VisibleForTesting
@ -71,9 +73,5 @@ public class TorrentPersistentRefreshKeyGenerator extends KeyGenerator {
this.lastAccess = LocalDateTime.now();
return this.peerId;
}
LocalDateTime getLastAccess() {
return lastAccess;
}
}
}

View file

@ -1,11 +1,12 @@
package org.araymond.joal.core.client.emulated.generator.key.algorithm;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Objects;
import lombok.EqualsAndHashCode;
import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException;
import java.util.concurrent.ThreadLocalRandom;
@EqualsAndHashCode
public class DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm implements KeyAlgorithm {
private final Long inclusiveLowerBound;
@ -38,18 +39,4 @@ public class DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm implements Ke
final long randomDigit = this.getRandomDigitBetween(this.inclusiveLowerBound, this.inclusiveUpperBound);
return Long.toHexString(randomDigit);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm that = (DigitRangeTransformedToHexWithoutLeadingZeroAlgorithm) o;
return Objects.equal(inclusiveLowerBound, that.inclusiveLowerBound) &&
Objects.equal(inclusiveUpperBound, that.inclusiveUpperBound);
}
@Override
public int hashCode() {
return Objects.hashCode(inclusiveLowerBound, inclusiveUpperBound);
}
}

View file

@ -1,12 +1,16 @@
package org.araymond.joal.core.client.emulated.generator.key.algorithm;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Objects;
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;
public HashKeyAlgorithm(
@ -19,26 +23,8 @@ public class HashKeyAlgorithm implements KeyAlgorithm {
this.length = length;
}
@JsonProperty("length")
public Integer getLength() {
return length;
}
@Override
public String generate() {
return RandomStringUtils.random(this.length, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final HashKeyAlgorithm that = (HashKeyAlgorithm) o;
return Objects.equal(length, that.length);
}
@Override
public int hashCode() {
return Objects.hashCode(length);
}
}

View file

@ -2,12 +2,16 @@ package org.araymond.joal.core.client.emulated.generator.key.algorithm;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.apache.commons.lang3.RandomStringUtils;
import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException;
@EqualsAndHashCode
@Getter
public class HashNoLeadingZeroKeyAlgorithm implements KeyAlgorithm {
@JsonProperty("length")
private final Integer length;
public HashNoLeadingZeroKeyAlgorithm(
@ -20,11 +24,6 @@ public class HashNoLeadingZeroKeyAlgorithm implements KeyAlgorithm {
this.length = length;
}
@JsonProperty("length")
public Integer getLength() {
return length;
}
@VisibleForTesting
String removeLeadingZeroes(final String string) {
return string.replaceFirst("^0+(?!$)", "");
@ -36,18 +35,4 @@ public class HashNoLeadingZeroKeyAlgorithm implements KeyAlgorithm {
RandomStringUtils.random(this.length, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final HashNoLeadingZeroKeyAlgorithm that = (HashNoLeadingZeroKeyAlgorithm) o;
return Objects.equal(length, that.length);
}
@Override
public int hashCode() {
return Objects.hashCode(length);
}
}

View file

@ -13,5 +13,4 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
public interface KeyAlgorithm {
String generate();
}

View file

@ -1,13 +1,17 @@
package org.araymond.joal.core.client.emulated.generator.key.algorithm;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Objects;
import com.mifmif.common.regex.Generex;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException;
@EqualsAndHashCode(of = "pattern")
public class RegexPatternKeyAlgorithm implements KeyAlgorithm {
@Getter
@JsonProperty("pattern")
private final String pattern;
private final Generex generex;
@ -21,27 +25,9 @@ public class RegexPatternKeyAlgorithm implements KeyAlgorithm {
this.generex = new Generex(pattern);
}
@JsonProperty("pattern")
public String getPattern() {
return pattern;
}
@Override
public String generate() {
return this.generex.random();
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RegexPatternKeyAlgorithm that = (RegexPatternKeyAlgorithm) o;
return Objects.equal(pattern, that.pattern);
}
@Override
public int hashCode() {
return Objects.hashCode(pattern);
}
}

View file

@ -1,12 +1,13 @@
package org.araymond.joal.core.client.emulated.generator.numwant;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.EqualsAndHashCode;
/**
* Created by raymo on 19/07/2017.
*/
@EqualsAndHashCode
public class NumwantProvider {
private final Integer numwant;
@ -27,18 +28,4 @@ public class NumwantProvider {
}
return numwant;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final NumwantProvider that = (NumwantProvider) o;
return Objects.equal(numwant, that.numwant) &&
Objects.equal(numwantOnStop, that.numwantOnStop);
}
@Override
public int hashCode() {
return Objects.hashCode(numwant, numwantOnStop);
}
}

View file

@ -19,7 +19,6 @@ public class NeverRefreshPeerIdGenerator extends PeerIdGenerator {
@JsonProperty(value = "shouldUrlEncode", required = true) final boolean isUrlEncoded
) {
super(algorithm, isUrlEncoded);
this.peerId = super.generatePeerId();
}
@ -27,5 +26,4 @@ public class NeverRefreshPeerIdGenerator extends PeerIdGenerator {
public String getPeerId(final InfoHash infoHash, final RequestEvent event) {
return peerId;
}
}

View file

@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException;
import org.araymond.joal.core.client.emulated.generator.peerid.generation.PeerIdAlgorithm;
import org.araymond.joal.core.torrent.torrent.InfoHash;
@ -20,9 +22,14 @@ import org.araymond.joal.core.torrent.torrent.InfoHash;
@JsonSubTypes.Type(value = TorrentVolatileRefreshPeerIdGenerator.class, name = "TORRENT_VOLATILE"),
@JsonSubTypes.Type(value = TorrentPersistentRefreshPeerIdGenerator.class, name = "TORRENT_PERSISTENT")
})
@EqualsAndHashCode
@Getter
public abstract class PeerIdGenerator {
public static final int PEER_ID_LENGTH = 20;
@JsonProperty("algorithm")
private final PeerIdAlgorithm algorithm;
@JsonProperty("shouldUrlEncode")
private final boolean shouldUrlEncode;
protected PeerIdGenerator(final PeerIdAlgorithm algorithm, final boolean shouldUrlEncode) {
@ -33,16 +40,6 @@ public abstract class PeerIdGenerator {
this.shouldUrlEncode = shouldUrlEncode;
}
@JsonProperty("algorithm")
PeerIdAlgorithm getAlgorithm() {
return algorithm;
}
@JsonProperty("shouldUrlEncode")
public boolean getShouldUrlEncoded() {
return shouldUrlEncode;
}
@JsonIgnore
public abstract String getPeerId(final InfoHash infoHash, RequestEvent event);
@ -53,19 +50,4 @@ public abstract class PeerIdGenerator {
}
return peerId;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final PeerIdGenerator peerIdGenerator = (PeerIdGenerator) o;
return shouldUrlEncode == peerIdGenerator.shouldUrlEncode &&
com.google.common.base.Objects.equal(algorithm, peerIdGenerator.algorithm);
}
@Override
public int hashCode() {
return com.google.common.base.Objects.hashCode(algorithm, shouldUrlEncode);
}
}

View file

@ -4,6 +4,7 @@ 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.peerid.generation.PeerIdAlgorithm;
import org.araymond.joal.core.torrent.torrent.InfoHash;
@ -19,6 +20,9 @@ public class TimedRefreshPeerIdGenerator extends PeerIdGenerator {
@VisibleForTesting
LocalDateTime lastGeneration;
private String peerId;
@JsonProperty("refreshEvery")
@Getter
private final Integer refreshEvery;
@JsonCreator
@ -34,11 +38,6 @@ public class TimedRefreshPeerIdGenerator extends PeerIdGenerator {
this.refreshEvery = refreshEvery;
}
@JsonProperty("refreshEvery")
Integer getRefreshEvery() {
return refreshEvery;
}
@Override
public String getPeerId(final InfoHash infoHash, final RequestEvent event) {
if (this.shouldRegeneratePeerId()) {

View file

@ -3,13 +3,14 @@ package org.araymond.joal.core.client.emulated.generator.peerid;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.Getter;
import org.araymond.joal.core.client.emulated.generator.peerid.generation.PeerIdAlgorithm;
import org.araymond.joal.core.torrent.torrent.InfoHash;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -47,7 +48,7 @@ public class TorrentPersistentRefreshPeerIdGenerator extends PeerIdGenerator {
@VisibleForTesting
void evictOldEntries() {
Sets.newHashSet(this.peerIdPerTorrent.entrySet()).stream()
new HashSet<>(this.peerIdPerTorrent.entrySet()).stream()
.filter(this::shouldEvictEntry)
.forEach(entry -> this.peerIdPerTorrent.remove(entry.getKey()));
}
@ -65,6 +66,7 @@ public class TorrentPersistentRefreshPeerIdGenerator extends PeerIdGenerator {
static class AccessAwarePeerId {
private final String peerId;
@Getter
private LocalDateTime lastAccess;
@VisibleForTesting
@ -77,9 +79,5 @@ public class TorrentPersistentRefreshPeerIdGenerator extends PeerIdGenerator {
this.lastAccess = LocalDateTime.now();
return this.peerId;
}
LocalDateTime getLastAccess() {
return lastAccess;
}
}
}

View file

@ -27,13 +27,8 @@ public class TorrentVolatileRefreshPeerIdGenerator extends PeerIdGenerator {
@Override
public String getPeerId(final InfoHash infoHash, final RequestEvent event) {
final String peerId;
if (!this.peerIdPerTorrent.containsKey(infoHash)) {
this.peerIdPerTorrent.put(infoHash, super.generatePeerId());
}
peerId = this.peerIdPerTorrent.get(infoHash);
this.peerIdPerTorrent.computeIfAbsent(infoHash, k -> super.generatePeerId());
String peerId = this.peerIdPerTorrent.get(infoHash);
if (event == RequestEvent.STOPPED) {
this.peerIdPerTorrent.remove(infoHash);

View file

@ -11,5 +11,4 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
public interface PeerIdAlgorithm {
String generate();
}

View file

@ -3,7 +3,8 @@ package org.araymond.joal.core.client.emulated.generator.peerid.generation;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.Objects;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException;
import org.araymond.joal.core.client.emulated.generator.peerid.PeerIdGenerator;
@ -11,13 +12,21 @@ import org.araymond.joal.core.client.emulated.generator.peerid.PeerIdGenerator;
import java.security.SecureRandom;
import java.time.Instant;
@EqualsAndHashCode(of = {"prefix", "charactersPool", "base"})
public class RandomPoolWithChecksumPeerIdAlgorithm implements PeerIdAlgorithm {
private final SecureRandom random;
private Integer refreshSeedAfter;
private Integer generationCount;
@JsonProperty("prefix")
@Getter
private final String prefix;
@JsonProperty("charactersPool")
@Getter
private final String charactersPool;
@JsonProperty("base")
@Getter
private final Integer base;
public RandomPoolWithChecksumPeerIdAlgorithm(
@ -44,21 +53,6 @@ public class RandomPoolWithChecksumPeerIdAlgorithm implements PeerIdAlgorithm {
this.base = base;
}
@JsonProperty("prefix")
public String getPrefix() {
return prefix;
}
@JsonProperty("charactersPool")
public String getCharactersPool() {
return charactersPool;
}
@JsonProperty("base")
public Integer getBase() {
return base;
}
@VisibleForTesting
byte[] createSecureRandomSeed() {
return Instant.now().toString().getBytes(Charsets.UTF_8);
@ -68,7 +62,7 @@ public class RandomPoolWithChecksumPeerIdAlgorithm implements PeerIdAlgorithm {
// 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);
return (randNumber + 10);
return randNumber + 10;
}
@VisibleForTesting
@ -105,19 +99,4 @@ public class RandomPoolWithChecksumPeerIdAlgorithm implements PeerIdAlgorithm {
buf[suffixLength - 1] = this.charactersPool.charAt(val);
return this.prefix + new String(buf);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RandomPoolWithChecksumPeerIdAlgorithm that = (RandomPoolWithChecksumPeerIdAlgorithm) o;
return Objects.equal(prefix, that.prefix) &&
Objects.equal(charactersPool, that.charactersPool) &&
Objects.equal(base, that.base);
}
@Override
public int hashCode() {
return Objects.hashCode(prefix, charactersPool, base);
}
}

View file

@ -1,12 +1,16 @@
package org.araymond.joal.core.client.emulated.generator.peerid.generation;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Objects;
import com.mifmif.common.regex.Generex;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.araymond.joal.core.client.emulated.TorrentClientConfigIntegrityException;
@EqualsAndHashCode(of = "pattern")
public class RegexPatternPeerIdAlgorithm implements PeerIdAlgorithm {
@JsonProperty("pattern")
@Getter
private final String pattern;
private final Generex generex;
@ -20,26 +24,8 @@ public class RegexPatternPeerIdAlgorithm implements PeerIdAlgorithm {
this.generex = new Generex(pattern);
}
@JsonProperty("pattern")
public String getPattern() {
return pattern;
}
@Override
public String generate() {
return this.generex.random();
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RegexPatternPeerIdAlgorithm that = (RegexPatternPeerIdAlgorithm) o;
return Objects.equal(pattern, that.pattern);
}
@Override
public int hashCode() {
return Objects.hashCode(pattern);
}
}

View file

@ -3,19 +3,24 @@ package org.araymond.joal.core.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Objects;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
/**
* Created by raymo on 24/01/2017.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@EqualsAndHashCode
@Getter
public class AppConfiguration {
private final Long minUploadRate;
private final Long maxUploadRate;
private final Integer simultaneousSeed;
@JsonProperty("client")
private final String client;
@JsonProperty("keepTorrentWithZeroLeechers")
private final boolean keepTorrentWithZeroLeechers;
@JsonCreator
@ -35,69 +40,29 @@ public class AppConfiguration {
validate();
}
public Long getMaxUploadRate() {
return maxUploadRate;
}
public Long getMinUploadRate() {
return minUploadRate;
}
public Integer getSimultaneousSeed() {
return simultaneousSeed;
}
@JsonProperty("client")
public String getClientFileName() {
return client;
}
@JsonProperty("keepTorrentWithZeroLeechers")
public boolean shouldKeepTorrentWithZeroLeechers() {
return keepTorrentWithZeroLeechers;
}
private void validate() {
if (java.util.Objects.isNull(minUploadRate)) {
if (minUploadRate == null) {
throw new AppConfigurationIntegrityException("minUploadRate must not be null");
}
if (minUploadRate < 0L) {
} else if (minUploadRate < 0L) {
throw new AppConfigurationIntegrityException("minUploadRate must be at least 0.");
}
if (java.util.Objects.isNull(maxUploadRate)) {
if (maxUploadRate == null) {
throw new AppConfigurationIntegrityException("maxUploadRate must not be null");
}
if (maxUploadRate < 0L) {
} else if (maxUploadRate < 0L) {
throw new AppConfigurationIntegrityException("maxUploadRate must greater or equal to 0.");
}
if (maxUploadRate < minUploadRate) {
} else if (maxUploadRate < minUploadRate) {
throw new AppConfigurationIntegrityException("maxUploadRate must be greater or equal to minUploadRate.");
}
if (java.util.Objects.isNull(simultaneousSeed)) {
if (simultaneousSeed == null) {
throw new AppConfigurationIntegrityException("simultaneousSeed must not be null");
}
if (simultaneousSeed < 1) {
} else 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.");
}
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final AppConfiguration that = (AppConfiguration) o;
return Objects.equal(minUploadRate, that.minUploadRate) &&
Objects.equal(maxUploadRate, that.maxUploadRate) &&
Objects.equal(simultaneousSeed, that.simultaneousSeed) &&
Objects.equal(client, that.client) &&
Objects.equal(keepTorrentWithZeroLeechers, that.keepTorrentWithZeroLeechers);
}
@Override
public int hashCode() {
return Objects.hashCode(minUploadRate, maxUploadRate, simultaneousSeed, client, keepTorrentWithZeroLeechers);
}
}

View file

@ -2,11 +2,10 @@ package org.araymond.joal.core.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.araymond.joal.core.SeedManager;
import org.araymond.joal.core.events.config.ConfigHasBeenLoadedEvent;
import org.araymond.joal.core.events.config.ConfigurationIsInDirtyStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import javax.inject.Provider;
@ -18,8 +17,8 @@ import java.nio.file.Path;
/**
* Created by raymo on 18/04/2017.
*/
@Slf4j
public class JoalConfigProvider implements Provider<AppConfiguration> {
private static final Logger logger = LoggerFactory.getLogger(JoalConfigProvider.class);
private static final String CONF_FILE_NAME = "config.json";
private final Path joalConfPath;
@ -32,13 +31,11 @@ public class JoalConfigProvider implements Provider<AppConfiguration> {
this.publisher = publisher;
this.joalConfPath = joalFoldersPath.getConfPath().resolve(CONF_FILE_NAME);
if (!Files.exists(joalConfPath)) {
throw new FileNotFoundException(String.format("App configuration file '%s' not found.", joalConfPath));
if (!Files.isRegularFile(joalConfPath)) {
throw new FileNotFoundException(String.format("App configuration file [%s] not found", joalConfPath));
}
if (logger.isDebugEnabled()) {
logger.debug("App configuration file will be searched for in {}", joalConfPath.toAbsolutePath());
}
log.debug("App configuration file will be searched for in [{}]", joalConfPath.toAbsolutePath());
}
public void init() {
@ -48,8 +45,8 @@ public class JoalConfigProvider implements Provider<AppConfiguration> {
@Override
public AppConfiguration get() {
if (this.config == null) {
logger.error("App configuration has not been loaded yet.");
throw new IllegalStateException("Attempted to get configuration before init.");
log.error("App configuration has not been loaded yet");
throw new IllegalStateException("Attempted to get configuration before init");
}
return this.config;
}
@ -58,16 +55,15 @@ public class JoalConfigProvider implements Provider<AppConfiguration> {
AppConfiguration loadConfiguration() {
final AppConfiguration configuration;
try {
if (logger.isDebugEnabled()) {
logger.debug("Reading json configuration from '{}'.", joalConfPath.toAbsolutePath());
}
log.debug("Reading json configuration from [{}]", joalConfPath.toAbsolutePath());
configuration = objectMapper.readValue(joalConfPath.toFile(), AppConfiguration.class);
logger.debug("Successfully red json configuration.");
log.debug("Successfully read json configuration");
} catch (final IOException e) {
logger.error("Failed to read configuration file", e);
log.error("Failed to read configuration file", e);
throw new IllegalStateException(e);
}
logger.info("App configuration has been successfully loaded.");
log.info("App configuration has been successfully loaded");
this.publisher.publishEvent(new ConfigHasBeenLoadedEvent(configuration));
return configuration;
}
@ -77,9 +73,8 @@ public class JoalConfigProvider implements Provider<AppConfiguration> {
objectMapper.writerWithDefaultPrettyPrinter().writeValue(joalConfPath.toFile(), conf);
publisher.publishEvent(new ConfigurationIsInDirtyStateEvent(conf));
} catch (final IOException e) {
logger.error("Failed to write new configuration file", e);
log.error("Failed to write new configuration file", e);
throw new IllegalStateException(e);
}
}
}

View file

@ -1,28 +1,14 @@
package org.araymond.joal.core.events.announce;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFacade;
@RequiredArgsConstructor
@Getter
public class FailedToAnnounceEvent {
private final AnnouncerFacade announcerFacade;
private final RequestEvent event;
private final String errMessage;
public FailedToAnnounceEvent(final AnnouncerFacade announcerFacade, final RequestEvent event, final String errMessage) {
this.announcerFacade = announcerFacade;
this.event = event;
this.errMessage = errMessage;
}
public AnnouncerFacade getAnnouncerFacade() {
return announcerFacade;
}
public RequestEvent getEvent() {
return event;
}
public String getErrMessage() {
return errMessage;
}
}

View file

@ -1,22 +1,13 @@
package org.araymond.joal.core.events.announce;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFacade;
@RequiredArgsConstructor
@Getter
public class SuccessfullyAnnounceEvent {
private final AnnouncerFacade announcerFacade;
private final RequestEvent event;
public SuccessfullyAnnounceEvent(final AnnouncerFacade announcerFacade, final RequestEvent event) {
this.announcerFacade = announcerFacade;
this.event = event;
}
public AnnouncerFacade getAnnouncerFacade() {
return announcerFacade;
}
public RequestEvent getEvent() {
return event;
}
}

View file

@ -1,15 +1,11 @@
package org.araymond.joal.core.events.announce;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFacade;
@RequiredArgsConstructor
@Getter
public class TooManyAnnouncesFailedEvent {
private final AnnouncerFacade announcerFacade;
public TooManyAnnouncesFailedEvent(final AnnouncerFacade announcerFacade) {
this.announcerFacade = announcerFacade;
}
public AnnouncerFacade getAnnouncerFacade() {
return announcerFacade;
}
}

View file

@ -1,22 +1,13 @@
package org.araymond.joal.core.events.announce;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFacade;
@RequiredArgsConstructor
@Getter
public class WillAnnounceEvent {
private final AnnouncerFacade announcerFacade;
private final RequestEvent event;
public WillAnnounceEvent(final AnnouncerFacade announcerFacade, final RequestEvent event) {
this.announcerFacade = announcerFacade;
this.event = event;
}
public AnnouncerFacade getAnnouncerFacade() {
return announcerFacade;
}
public RequestEvent getEvent() {
return event;
}
}

View file

@ -1,11 +1,13 @@
package org.araymond.joal.core.events.config;
import com.google.common.base.Preconditions;
import lombok.Getter;
import org.araymond.joal.core.config.AppConfiguration;
/**
* Created by raymo on 08/07/2017.
*/
@Getter
public class ConfigHasBeenLoadedEvent {
private final AppConfiguration configuration;
@ -13,8 +15,4 @@ public class ConfigHasBeenLoadedEvent {
Preconditions.checkNotNull(configuration, "Configuration must not be null.");
this.configuration = configuration;
}
public AppConfiguration getConfiguration() {
return configuration;
}
}

View file

@ -1,11 +1,14 @@
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;
/**
* Created by raymo on 09/07/2017.
*/
@Getter
public class ConfigurationIsInDirtyStateEvent {
private final AppConfiguration configuration;
@ -13,8 +16,4 @@ public class ConfigurationIsInDirtyStateEvent {
Preconditions.checkNotNull(configuration, "Configuration must not be null.");
this.configuration = configuration;
}
public AppConfiguration getConfiguration() {
return configuration;
}
}

View file

@ -1,9 +1,11 @@
package org.araymond.joal.core.events.config;
import com.google.common.base.Preconditions;
import lombok.Getter;
import java.util.List;
@Getter
public class ListOfClientFilesEvent {
private final List<String> clients;
@ -11,8 +13,4 @@ public class ListOfClientFilesEvent {
Preconditions.checkNotNull(clients, "Clients list must not be null");
this.clients = clients;
}
public List<String> getClients() {
return clients;
}
}

View file

@ -1,8 +1,10 @@
package org.araymond.joal.core.events.global.state;
import com.google.common.base.Preconditions;
import lombok.Getter;
import org.araymond.joal.core.client.emulated.BitTorrentClient;
@Getter
public class GlobalSeedStartedEvent {
private final BitTorrentClient bitTorrentClient;
@ -10,8 +12,4 @@ public class GlobalSeedStartedEvent {
Preconditions.checkNotNull(bitTorrentClient, "BitTorrentClient cannot be null");
this.bitTorrentClient = bitTorrentClient;
}
public BitTorrentClient getBitTorrentClient() {
return bitTorrentClient;
}
}

View file

@ -1,19 +1,17 @@
package org.araymond.joal.core.events.speed;
import com.google.common.collect.Maps;
import lombok.Getter;
import org.araymond.joal.core.bandwith.Speed;
import org.araymond.joal.core.torrent.torrent.InfoHash;
import java.util.HashMap;
import java.util.Map;
@Getter
public class SeedingSpeedsHasChangedEvent {
private final Map<InfoHash, Speed> speeds;
public SeedingSpeedsHasChangedEvent(final Map<InfoHash, Speed> speeds) {
this.speeds = Maps.newHashMap(speeds);
}
public Map<InfoHash, Speed> getSpeeds() {
return speeds;
this.speeds = new HashMap<>(speeds);
}
}

View file

@ -1,10 +1,12 @@
package org.araymond.joal.core.events.torrent.files;
import com.google.common.base.Preconditions;
import lombok.Getter;
/**
* Created by raymo on 06/05/2017.
*/
@Getter
public class FailedToAddTorrentFileEvent {
private final String fileName;
private final String error;
@ -14,12 +16,4 @@ public class FailedToAddTorrentFileEvent {
this.fileName = fileName;
this.error = error;
}
public String getFileName() {
return fileName;
}
public String getError() {
return error;
}
}

View file

@ -1,11 +1,13 @@
package org.araymond.joal.core.events.torrent.files;
import com.google.common.base.Preconditions;
import lombok.Getter;
import org.araymond.joal.core.torrent.torrent.MockedTorrent;
/**
* Created by raymo on 06/05/2017.
*/
@Getter
public class TorrentFileAddedEvent {
private final MockedTorrent torrent;
@ -13,8 +15,4 @@ public class TorrentFileAddedEvent {
Preconditions.checkNotNull(torrent, "MockedTorrent cannot be null.");
this.torrent = torrent;
}
public MockedTorrent getTorrent() {
return torrent;
}
}

View file

@ -1,11 +1,13 @@
package org.araymond.joal.core.events.torrent.files;
import com.google.common.base.Preconditions;
import lombok.Getter;
import org.araymond.joal.core.torrent.torrent.MockedTorrent;
/**
* Created by raymo on 06/05/2017.
*/
@Getter
public class TorrentFileDeletedEvent {
private final MockedTorrent torrent;
@ -13,8 +15,4 @@ public class TorrentFileDeletedEvent {
Preconditions.checkNotNull(torrent, "MockedTorrent cannot be null.");
this.torrent = torrent;
}
public MockedTorrent getTorrent() {
return torrent;
}
}

View file

@ -9,5 +9,4 @@ public class UnrecognizedClientPlaceholder extends RuntimeException {
public UnrecognizedClientPlaceholder(final String message) {
super(message);
}
}

View file

@ -1,32 +1,22 @@
package org.araymond.joal.core.torrent.torrent;
import com.google.common.base.Objects;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
@ToString
@EqualsAndHashCode(of = "infoHash")
@Getter
public class InfoHash {
private final String infoHash;
private final String humanReadable;
public InfoHash(final byte[] bytes) {
this.infoHash = new String(bytes, MockedTorrent.BYTE_ENCODING);
}
public String humanReadableValue() {
return infoHash.replaceAll("\\p{C}", "");
this.humanReadable = infoHash.replaceAll("\\p{C}", "");
}
public String value() {
return infoHash;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final InfoHash infoHash1 = (InfoHash) o;
return Objects.equal(infoHash, infoHash1.infoHash);
}
@Override
public int hashCode() {
return Objects.hashCode(infoHash);
}
}

View file

@ -1,9 +1,9 @@
package org.araymond.joal.core.torrent.torrent;
import com.google.common.base.Charsets;
import com.google.common.base.Objects;
import com.turn.ttorrent.bcodec.InvalidBEncodingException;
import com.turn.ttorrent.common.Torrent;
import lombok.EqualsAndHashCode;
import org.apache.commons.io.FileUtils;
import java.io.File;
@ -16,6 +16,7 @@ import java.security.NoSuchAlgorithmException;
* Created by raymo on 23/01/2017.
*/
@SuppressWarnings("ClassWithOnlyPrivateConstructors")
@EqualsAndHashCode
public class MockedTorrent extends Torrent {
public static final Charset BYTE_ENCODING = Charsets.ISO_8859_1;
@ -60,17 +61,4 @@ public class MockedTorrent extends Torrent {
public static MockedTorrent fromBytes(final byte[] bytes) throws IOException, NoSuchAlgorithmException {
return new MockedTorrent(bytes, false);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MockedTorrent that = (MockedTorrent) o;
return Objects.equal(this.infoHash, that.infoHash);
}
@Override
public int hashCode() {
return Objects.hashCode(this.infoHash);
}
}

View file

@ -7,8 +7,8 @@ import org.araymond.joal.core.torrent.torrent.MockedTorrent;
*/
public interface TorrentFileChangeAware {
void onTorrentFileAdded(final MockedTorrent torrent);
void onTorrentFileAdded(MockedTorrent torrent);
void onTorrentFileRemoved(final MockedTorrent torrent);
void onTorrentFileRemoved(MockedTorrent torrent);
}

View file

@ -2,14 +2,12 @@ package org.araymond.joal.core.torrent.watcher;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.araymond.joal.core.SeedManager;
import org.araymond.joal.core.exception.NoMoreTorrentsFileAvailableException;
import org.araymond.joal.core.torrent.torrent.InfoHash;
import org.araymond.joal.core.torrent.torrent.MockedTorrent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
@ -18,28 +16,50 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.concurrent.ThreadLocalRandom;
import static java.lang.String.format;
import static java.util.Optional.ofNullable;
/**
* Created by raymo on 28/01/2017.
*/
@Slf4j
public class TorrentFileProvider extends FileAlterationListenerAdaptor {
private static final Logger logger = LoggerFactory.getLogger(TorrentFileProvider.class);
private final TorrentFileWatcher watcher;
private final Map<File, MockedTorrent> torrentFiles;
private final Map<File, MockedTorrent> torrentFiles = Collections.synchronizedMap(new HashMap<>());
private final Set<TorrentFileChangeAware> torrentFileChangeListener;
private final Path torrentFolder;
private final Path archiveFolder;
public TorrentFileProvider(final SeedManager.JoalFoldersPath joalFoldersPath) throws FileNotFoundException {
Path torrentFolder = joalFoldersPath.getTorrentFilesPath();
if (!Files.isDirectory(torrentFolder)) {
// 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()));
}
this.archiveFolder = joalFoldersPath.getTorrentArchivedPath();
this.watcher = new TorrentFileWatcher(this, torrentFolder);
this.torrentFileChangeListener = new HashSet<>();
}
@VisibleForTesting
void init() {
if (!Files.exists(archiveFolder)) {
if (!Files.isDirectory(archiveFolder)) {
if (Files.exists(archiveFolder)) {
String errMsg = "archive folder exists, but is not a directory";
log.error(errMsg);
throw new IllegalStateException(errMsg);
}
try {
Files.createDirectory(archiveFolder);
} catch (final IOException e) {
logger.error("Failed to create archive folder.", e);
throw new IllegalStateException("Failed to create archive folder.", e);
String errMsg = "Failed to create archive folder";
log.error(errMsg, e);
throw new IllegalStateException(errMsg, e);
}
}
}
@ -54,55 +74,35 @@ public class TorrentFileProvider extends FileAlterationListenerAdaptor {
this.torrentFiles.clear();
}
public TorrentFileProvider(final SeedManager.JoalFoldersPath joalFoldersPath) throws FileNotFoundException {
this.torrentFolder = joalFoldersPath.getTorrentFilesPath();
if (!Files.exists(torrentFolder)) {
logger.error("Folder " + torrentFolder.toAbsolutePath() + " does not exists.");
throw new FileNotFoundException(String.format("Torrent folder '%s' not found.", torrentFolder.toAbsolutePath()));
}
this.archiveFolder = joalFoldersPath.getTorrentArchivedPath();
this.torrentFiles = Collections.synchronizedMap(new HashMap<>());
this.watcher = new TorrentFileWatcher(this, torrentFolder);
this.torrentFileChangeListener = new HashSet<>();
}
@Override
public void onFileDelete(final File file) {
if (!this.torrentFiles.containsKey(file)) {
return;
}
final MockedTorrent torrent = this.torrentFiles.get(file);
if (torrent == null) {
return;
}
logger.info("Torrent file deleting detected, hot deleted file: {}", file.getAbsolutePath());
this.torrentFiles.remove(file);
this.torrentFileChangeListener.forEach(listener -> listener.onTorrentFileRemoved(torrent));
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));
});
}
@Override
public void onFileCreate(final File file) {
logger.info("Torrent file addition detected, hot creating file: {}", file.getAbsolutePath());
log.info("Torrent file addition detected, hot creating file [{}]", file.getAbsolutePath());
try {
final MockedTorrent torrent = MockedTorrent.fromFile(file);
this.torrentFiles.put(file, torrent);
this.torrentFileChangeListener.forEach(listener -> listener.onTorrentFileAdded(torrent));
} catch (final IOException | NoSuchAlgorithmException e) {
logger.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
logger.warn("Unexpected exception was caught for file '{}', moved to archive folder.", file.getAbsolutePath(), e);
log.warn("Unexpected exception was caught for file [{}], moved to archive folder", file.getAbsolutePath(), e);
this.moveToArchiveFolder(file);
}
}
@Override
public void onFileChange(final File file) {
logger.info("Torrent file change detected, hot reloading file: {}", file.getAbsolutePath());
log.info("Torrent file change detected, hot reloading file [{}]", file.getAbsolutePath());
this.onFileDelete(file);
this.onFileCreate(file);
}
@ -120,11 +120,8 @@ public class TorrentFileProvider extends FileAlterationListenerAdaptor {
return this.torrentFiles.values().stream()
.filter(torrent -> !unwantedTorrents.contains(torrent.getTorrentInfoHash()))
.collect(Collectors.collectingAndThen(Collectors.toList(), collected -> {
Collections.shuffle(collected);
return collected.stream();
}))
.findFirst()
.sorted((o1, o2) -> ThreadLocalRandom.current().nextInt(-1, 2))
.findAny()
.orElseThrow(() -> new NoMoreTorrentsFileAvailableException("No more torrent file available."));
}
@ -135,24 +132,22 @@ public class TorrentFileProvider extends FileAlterationListenerAdaptor {
this.onFileDelete(torrentFile);
try {
Files.deleteIfExists(archiveFolder.resolve(torrentFile.getName()));
Files.move(torrentFile.toPath(), archiveFolder.resolve(torrentFile.getName()));
logger.info("Successfully moved file: {} to archive folder", torrentFile.getAbsolutePath());
Path moveTarget = archiveFolder.resolve(torrentFile.getName());
Files.deleteIfExists(moveTarget);
Files.move(torrentFile.toPath(), moveTarget);
log.info("Successfully moved file [{}] to archive folder", torrentFile.getAbsolutePath());
} catch (final IOException e) {
logger.warn("Failed to archive file: {}, the file won't be used anymore for the current session, but it remains on the folder.", 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());
}
}
public void moveToArchiveFolder(final InfoHash infoHash) {
final Optional<File> first = this.torrentFiles.entrySet().stream()
this.torrentFiles.entrySet().stream()
.filter(entry -> entry.getValue().getTorrentInfoHash().equals(infoHash))
.map(Map.Entry::getKey)
.findFirst();
if (first.isPresent()) {
this.moveToArchiveFolder(first.get());
} else {
logger.warn("Cannot move torrent {} to archive folder. Torrent file seems not to be registered in TorrentFileProvider.", infoHash);
}
.findAny()
.ifPresentOrElse(this::moveToArchiveFolder,
() -> log.warn("Cannot move torrent [{}] to archive folder. Torrent file seems not to be registered in TorrentFileProvider", infoHash));
}
public int getTorrentCount() {
@ -160,7 +155,6 @@ public class TorrentFileProvider extends FileAlterationListenerAdaptor {
}
public List<MockedTorrent> getTorrentFiles() {
return Lists.newArrayList(this.torrentFiles.values());
return new ArrayList<>(this.torrentFiles.values());
}
}

View file

@ -2,26 +2,28 @@ package org.araymond.joal.core.torrent.watcher;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* Hooks up a directory listener to detect torrent file additions,
* deletions, changes.
*
* Created by raymo on 01/05/2017.
*/
@Slf4j
class TorrentFileWatcher {
private static final Logger logger = LoggerFactory.getLogger(TorrentFileWatcher.class);
// 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 = 2 * 1000;
private static final Integer DEFAULT_SCAN_INTERVAL_MS = 2 * 1000;
private static final IOFileFilter TORRENT_FILE_FILTER = FileFilterUtils.suffixFileFilter(".torrent");
private final FileAlterationObserver observer;
@ -30,23 +32,24 @@ class TorrentFileWatcher {
private final FileAlterationMonitor monitor;
TorrentFileWatcher(final FileAlterationListener listener, final Path monitoredFolder) {
this(listener, monitoredFolder, DEFAULT_SCAN_INTERVAL);
this(listener, monitoredFolder, DEFAULT_SCAN_INTERVAL_MS);
}
TorrentFileWatcher(final FileAlterationListener listener, final Path monitoredFolder, final Integer interval) {
TorrentFileWatcher(final FileAlterationListener listener, final Path monitoredFolder, final Integer 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(interval, "interval cannot be null");
Preconditions.checkArgument(interval > 0, "interval cannot be less than 1");
Preconditions.checkArgument(Files.exists(monitoredFolder), "Folder [" + monitoredFolder.toAbsolutePath() + "] does not exists.");
Preconditions.checkNotNull(intervalMs, "intervalMs cannot be null");
Preconditions.checkArgument(intervalMs > 0, "intervalMs cannot be less than 1");
this.listener = listener;
this.monitoredFolder = monitoredFolder.toFile();
this.monitor = new FileAlterationMonitor(interval);
this.monitor.setThreadFactory(new ThreadFactoryBuilder().setNameFormat("torrent-file-watcher-%d").build());
this.observer = new FileAlterationObserver(this.monitoredFolder, TORRENT_FILE_FILTER);
this.monitoredFolder = monitoredFolder.toFile();
this.observer = new FileAlterationObserver(this.monitoredFolder, TORRENT_FILE_FILTER);
this.observer.addListener(this.listener);
monitor.addObserver(this.observer);
this.monitor = new FileAlterationMonitor(intervalMs);
this.monitor.setThreadFactory(new ThreadFactoryBuilder().setNameFormat("torrent-file-watcher-%d").build());
this.monitor.addObserver(this.observer);
}
void start() {
@ -54,21 +57,21 @@ class TorrentFileWatcher {
this.monitor.start();
// Trigger event for already present file
FileUtils.listFiles(this.monitoredFolder, TorrentFileWatcher.TORRENT_FILE_FILTER, null)
.forEach(listener::onFileCreate);
.forEach(this.listener::onFileCreate);
} catch (final Exception e) {
logger.error("Failed to start torrent file monitoring.", e);
throw new IllegalStateException("Failed to start torrent file monitoring.", e);
log.error("Failed to start torrent file monitoring", e);
throw new IllegalStateException("Failed to start torrent file monitoring", e);
}
}
void stop() {
logger.trace("Stopping TorrentFileProvider.");
log.trace("Stopping TorrentFileProvider");
this.observer.getListeners().forEach(observer::removeListener);
try {
this.monitor.stop(10);
} catch (final Exception ignored) {
}
logger.trace("TorrentFileProvider stopped.");
}
log.trace("TorrentFileProvider stopped");
}
}

View file

@ -2,7 +2,6 @@ package org.araymond.joal.core.ttorrent.client;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import org.araymond.joal.core.config.AppConfiguration;
import org.araymond.joal.core.events.torrent.files.TorrentFileAddedEvent;
@ -17,20 +16,16 @@ import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFacade;
import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFactory;
import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceRequest;
import org.araymond.joal.core.ttorrent.client.announcer.request.AnnouncerExecutor;
import org.slf4j.Logger;
import org.springframework.context.ApplicationEventPublisher;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import static org.slf4j.LoggerFactory.getLogger;
import static java.util.stream.Collectors.toList;
public class Client implements TorrentFileChangeAware, ClientFacade {
private static final Logger logger = getLogger(Client.class);
private final AppConfiguration appConfiguration;
private final TorrentFileProvider torrentFileProvider;
private final ApplicationEventPublisher eventPublisher;
@ -85,10 +80,10 @@ public class Client implements TorrentFileChangeAware, ClientFacade {
}
}
});
for (int i = 0; i < this.appConfiguration.getSimultaneousSeed(); i++) {
try {
this.lock.writeLock().lock();
this.addTorrent();
} catch (final NoMoreTorrentsFileAvailableException ignored) {
break;
@ -107,7 +102,7 @@ public class Client implements TorrentFileChangeAware, ClientFacade {
final MockedTorrent torrent = this.torrentFileProvider.getTorrentNotIn(
this.currentlySeedingAnnouncer.stream()
.map(Announcer::getTorrentInfoHash)
.collect(Collectors.toList())
.collect(toList())
);
final Announcer announcer = this.announcerFactory.create(torrent);
this.currentlySeedingAnnouncer.add(announcer);
@ -158,7 +153,7 @@ public class Client implements TorrentFileChangeAware, ClientFacade {
}
public void onNoMorePeers(final InfoHash infoHash) {
if (!this.appConfiguration.shouldKeepTorrentWithZeroLeechers()) {
if (!this.appConfiguration.isKeepTorrentWithZeroLeechers()) {
this.torrentFileProvider.moveToArchiveFolder(infoHash);
}
}
@ -218,7 +213,7 @@ public class Client implements TorrentFileChangeAware, ClientFacade {
public List<AnnouncerFacade> getCurrentlySeedingAnnouncer() {
try {
this.lock.readLock().lock();
return Lists.newArrayList(this.currentlySeedingAnnouncer);
return new ArrayList<>(this.currentlySeedingAnnouncer);
} finally {
this.lock.readLock().unlock();
}

View file

@ -18,8 +18,9 @@ public final class ClientBuilder {
private DelayQueue<AnnounceRequest> delayQueue;
private ClientBuilder() {
// private
}
public static ClientBuilder builder() {
return new ClientBuilder();
}

View file

@ -3,8 +3,8 @@ 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
@ -19,14 +19,14 @@ import java.util.Optional;
/**
* Created by raymo on 23/01/2017.
*/
@Slf4j
public class ConnectionHandler {
private static final Logger logger = LoggerFactory.getLogger(ConnectionHandler.class);
public static final int PORT_RANGE_START = 49152;
public static final int PORT_RANGE_END = 65534;
private ServerSocketChannel channel;
@Getter
private InetAddress ipAddress;
private Thread ipFetcherThread;
private static final String[] IP_PROVIDERS = new String[]{
@ -36,13 +36,6 @@ public class ConnectionHandler {
"http://icanhazip.com/"
};
public ConnectionHandler() {
}
public InetAddress getIpAddress() {
return this.ipAddress;
}
public int getPort() {
return this.channel.socket().getLocalPort();
}
@ -50,10 +43,10 @@ public class ConnectionHandler {
public void start() throws IOException {
this.channel = this.bindToPort();
final int port = this.channel.socket().getLocalPort();
logger.info("Listening for incoming peer connections on port {}.", port);
log.info("Listening for incoming peer connections on port {}.", port);
this.ipAddress = fetchIp();
logger.info("Ip reported to tracker will be: {}", this.getIpAddress().getHostAddress());
log.info("Ip reported to tracker will be: {}", this.getIpAddress().getHostAddress());
this.ipFetcherThread = new Thread(() -> {
while (this.ipFetcherThread == null || !this.ipFetcherThread.isInterrupted()) {
@ -62,9 +55,9 @@ public class ConnectionHandler {
Thread.sleep(1000 * 5400);
this.ipAddress = this.fetchIp();
} catch (final UnknownHostException e) {
logger.warn("Faield to fetch Ip", e);
log.warn("Faield to fetch Ip", e);
} catch (final InterruptedException e) {
logger.info("Ip fetcher thread has been stopped.");
log.info("Ip fetcher thread has been stopped.");
}
}
});
@ -78,7 +71,7 @@ public class ConnectionHandler {
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");
try (final BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), Charsets.UTF_8))) {
return InetAddress.getByName(in.readLine());
}finally {
} finally {
// Ensure all streams associated with http connection are closed
final InputStream err = ((HttpURLConnection) urlConnection).getErrorStream();
try { if (err != null) err.close(); }
@ -92,11 +85,11 @@ public class ConnectionHandler {
Collections.shuffle(shuffledList);
for (final String ipProvider : shuffledList) {
logger.info("Fetching ip from: " + ipProvider);
log.info("Fetching ip from: " + ipProvider);
try {
return Optional.of(this.readIpFromProvider(ipProvider));
} catch (final IOException e) {
logger.warn("Failed to fetch Ip from \"" + ipProvider + "\"", e);
log.warn("Failed to fetch Ip from [" + ipProvider + "]", e);
}
}
@ -107,14 +100,14 @@ public class ConnectionHandler {
InetAddress fetchIp() throws UnknownHostException {
final Optional<InetAddress> ip = this.tryToFetchFromProviders();
if (ip.isPresent()) {
logger.info("Successfully fetch public IP address: {}", ip.get().getHostAddress());
log.info("Successfully fetch public IP address: {}", ip.get().getHostAddress());
return ip.get();
}
if (this.ipAddress != null) {
logger.warn("Failed to fetch public IP address, reuse last known IP address: {}", this.ipAddress.getHostAddress());
log.warn("Failed to fetch public IP address, reuse last known IP address: {}", this.ipAddress.getHostAddress());
return this.ipAddress;
}
logger.warn("Failed to fetch public IP address, fallback to localhost");
log.warn("Failed to fetch public IP address, fallback to localhost");
return InetAddress.getLocalHost();
}
@ -126,7 +119,6 @@ public class ConnectionHandler {
for (int port = ConnectionHandler.PORT_RANGE_START; port <= ConnectionHandler.PORT_RANGE_END; port++) {
final InetSocketAddress tryAddress = new InetSocketAddress(port);
try {
channel = ServerSocketChannel.open();
channel.socket().bind(tryAddress);
@ -134,7 +126,7 @@ public class ConnectionHandler {
break;
} catch (final IOException ioe) {
// Ignore, try next port
logger.warn("Could not bind to port {}: {}, trying next port...", tryAddress.getPort(), ioe.getMessage());
log.warn("Could not bind to port {}: {}, trying next port...", tryAddress.getPort(), ioe.getMessage());
try {
if (channel != null) channel.close();
} catch (final IOException ignored) {
@ -149,13 +141,13 @@ public class ConnectionHandler {
}
public void close() {
logger.debug("Call to close ConnectionHandler.");
log.debug("Call to close ConnectionHandler.");
try {
if (this.channel != null) {
this.channel.close();
}
} catch (final Exception e) {
logger.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;
}
@ -166,7 +158,7 @@ public class ConnectionHandler {
} finally {
this.ipFetcherThread = null;
}
logger.debug("ConnectionHandler closed.");
log.debug("ConnectionHandler closed.");
}
}

View file

@ -1,5 +1,7 @@
package org.araymond.joal.core.ttorrent.client;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.araymond.joal.core.torrent.torrent.InfoHash;
import java.time.LocalDateTime;
@ -76,20 +78,12 @@ public class DelayQueue<T extends DelayQueue.InfoHashAble> {
}
@RequiredArgsConstructor
private static final class IntervalAware<T> implements Comparable<IntervalAware> {
private final LocalDateTime releaseAt;
@Getter
private final T item;
private final LocalDateTime releaseAt;
private IntervalAware(final T item, final LocalDateTime releaseAt) {
this.item = item;
this.releaseAt = releaseAt;
}
public T getItem() {
return item;
}
@SuppressWarnings("NullableProblems")
@Override
public int compareTo(final IntervalAware o) {
return this.releaseAt.compareTo(o.releaseAt);

View file

@ -4,6 +4,8 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.turn.ttorrent.client.announce.AnnounceException;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.Getter;
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;
@ -13,8 +15,6 @@ import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceR
import org.araymond.joal.core.ttorrent.client.announcer.tracker.TrackerClient;
import org.araymond.joal.core.ttorrent.client.announcer.tracker.TrackerClientUriProvider;
import org.araymond.joal.core.ttorrent.client.announcer.tracker.TrackerResponseHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.time.LocalDateTime;
@ -23,14 +23,16 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
public class Announcer implements AnnouncerFacade {
private static final Logger logger = LoggerFactory.getLogger(Announcer.class);
@Getter
private int lastKnownInterval = 5;
@Getter
private int consecutiveFails = 0;
private Integer lastKnownLeechers = null;
private Integer lastKnownSeeders = null;
private LocalDateTime lastAnnouncedAt = null;
@Getter
private final MockedTorrent torrent;
private TrackerClient trackerClient;
private final AnnounceDataAccessor announceDataAccessor;
@ -42,7 +44,7 @@ public class Announcer implements AnnouncerFacade {
}
private TrackerClient buildTrackerClient(final MockedTorrent torrent, HttpClient httpClient) {
final List<URI> trackerURIs = torrent.getAnnounceList().stream() // Use a list to keep it ordered
final List<URI> trackerURIs = torrent.getAnnounceList().stream() // Use a list to keep it ordered
.sequential()
.flatMap(Collection::stream)
.collect(Collectors.toList());
@ -55,8 +57,8 @@ public class Announcer implements AnnouncerFacade {
}
public SuccessAnnounceResponse announce(final RequestEvent event) throws AnnounceException, TooMuchAnnouncesFailedInARawException {
if (logger.isDebugEnabled()) {
logger.debug("Attempt to announce {} for {}", event.getEventName(), this.torrent.getTorrentInfoHash().humanReadableValue());
if (log.isDebugEnabled()) {
log.debug("Attempt to announce {} for {}", event.getEventName(), this.torrent.getTorrentInfoHash().getHumanReadable());
}
try {
@ -65,8 +67,8 @@ public class Announcer implements AnnouncerFacade {
this.announceDataAccessor.getHttpRequestQueryForTorrent(this.torrent.getTorrentInfoHash(), event),
this.announceDataAccessor.getHttpHeadersForTorrent()
);
if (logger.isInfoEnabled()) {
logger.info("{} has announced successfully. Response: {} seeders, {} leechers, {}s interval", this.torrent.getTorrentInfoHash().humanReadableValue(), responseMessage.getSeeders(), responseMessage.getLeechers(), responseMessage.getInterval());
if (log.isInfoEnabled()) {
log.info("{} has announced successfully. Response: {} seeders, {} leechers, {}s interval", this.torrent.getTorrentInfoHash().getHumanReadable(), responseMessage.getSeeders(), responseMessage.getLeechers(), responseMessage.getInterval());
}
this.lastKnownInterval = responseMessage.getInterval();
@ -76,14 +78,14 @@ public class Announcer implements AnnouncerFacade {
return responseMessage;
} catch (final Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("{} has failed to announce", this.torrent.getTorrentInfoHash().humanReadableValue(), e);
if (log.isWarnEnabled()) {
log.warn("{} has failed to announce", this.torrent.getTorrentInfoHash().getHumanReadable(), e);
}
++this.consecutiveFails;
if (this.consecutiveFails >= 5) {
if (logger.isInfoEnabled()) {
logger.info("{} has failed to announce 5 times in a raw", this.torrent.getTorrentInfoHash().humanReadableValue());
if (log.isInfoEnabled()) {
log.info("{} has failed to announce 5 times in a raw", this.torrent.getTorrentInfoHash().getHumanReadable());
}
throw new TooMuchAnnouncesFailedInARawException(torrent);
}
@ -91,16 +93,6 @@ public class Announcer implements AnnouncerFacade {
}
}
@Override
public int getLastKnownInterval() {
return lastKnownInterval;
}
@Override
public int getConsecutiveFails() {
return consecutiveFails;
}
@Override
public Optional<Integer> getLastKnownLeechers() {
return Optional.ofNullable(lastKnownLeechers);
@ -116,10 +108,6 @@ public class Announcer implements AnnouncerFacade {
return Optional.ofNullable(lastAnnouncedAt);
}
public MockedTorrent getTorrent() {
return torrent;
}
@Override
public String getTorrentName() {
return this.torrent.getName();

View file

@ -1,20 +1,16 @@
package org.araymond.joal.core.ttorrent.client.announcer;
import lombok.RequiredArgsConstructor;
import org.apache.http.client.HttpClient;
import org.araymond.joal.core.torrent.torrent.MockedTorrent;
import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceDataAccessor;
@RequiredArgsConstructor
public class AnnouncerFactory {
private final AnnounceDataAccessor announceDataAccessor;
private final HttpClient httpClient;
public AnnouncerFactory(final AnnounceDataAccessor announceDataAccessor, HttpClient httpClient) {
this.announceDataAccessor = announceDataAccessor;
this.httpClient = httpClient;
}
public Announcer create(final MockedTorrent torrent) {
return new Announcer(torrent, this.announceDataAccessor, httpClient);
}
}

View file

@ -1,7 +1,9 @@
package org.araymond.joal.core.ttorrent.client.announcer.exceptions;
import lombok.Getter;
import org.araymond.joal.core.torrent.torrent.MockedTorrent;
@Getter
public class TooMuchAnnouncesFailedInARawException extends Exception {
private static final long serialVersionUID = 1864953989056739188L;
@ -11,8 +13,4 @@ public class TooMuchAnnouncesFailedInARawException extends Exception {
super();
this.torrent = torrent;
}
public MockedTorrent getTorrent() {
return torrent;
}
}

View file

@ -1,6 +1,7 @@
package org.araymond.joal.core.ttorrent.client.announcer.request;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.RequiredArgsConstructor;
import org.araymond.joal.core.bandwith.BandwidthDispatcherFacade;
import org.araymond.joal.core.client.emulated.BitTorrentClient;
import org.araymond.joal.core.torrent.torrent.InfoHash;
@ -9,18 +10,13 @@ import org.araymond.joal.core.ttorrent.client.ConnectionHandler;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
public class AnnounceDataAccessor {
private final BitTorrentClient bitTorrentClient;
private final BandwidthDispatcherFacade bandwidthDispatcher;
private final ConnectionHandler connectionHandler;
public AnnounceDataAccessor(final BitTorrentClient bitTorrentClient, final BandwidthDispatcherFacade bandwidthDispatcher, final ConnectionHandler connectionHandler) {
this.bitTorrentClient = bitTorrentClient;
this.bandwidthDispatcher = bandwidthDispatcher;
this.connectionHandler = connectionHandler;
}
public String getHttpRequestQueryForTorrent(final InfoHash infoHash, final RequestEvent event) {
return this.bitTorrentClient.createRequestQuery(event, infoHash, this.bandwidthDispatcher.getSeedStatForTorrent(infoHash), this.connectionHandler);
}
@ -28,5 +24,4 @@ public class AnnounceDataAccessor {
public List<Map.Entry<String, String>> getHttpHeadersForTorrent() {
return this.bitTorrentClient.createRequestHeaders();
}
}

View file

@ -1,20 +1,19 @@
package org.araymond.joal.core.ttorrent.client.announcer.request;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.araymond.joal.core.torrent.torrent.InfoHash;
import org.araymond.joal.core.ttorrent.client.DelayQueue;
import org.araymond.joal.core.ttorrent.client.announcer.Announcer;
@RequiredArgsConstructor
@Getter
public final class AnnounceRequest implements DelayQueue.InfoHashAble {
private final Announcer announcer;
private final RequestEvent event;
private AnnounceRequest(final Announcer announcer, final RequestEvent event) {
this.announcer = announcer;
this.event = event;
}
public static AnnounceRequest createStart(final Announcer announcer) {
return new AnnounceRequest(announcer, RequestEvent.STARTED);
}
@ -27,17 +26,8 @@ public final class AnnounceRequest implements DelayQueue.InfoHashAble {
return new AnnounceRequest(announcer, RequestEvent.STOPPED);
}
public Announcer getAnnouncer() {
return this.announcer;
}
public RequestEvent getEvent() {
return this.event;
}
@Override
public InfoHash getInfoHash() {
return this.announcer.getTorrentInfoHash();
}
}

View file

@ -1,20 +1,19 @@
package org.araymond.joal.core.ttorrent.client.announcer.request;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.Getter;
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.response.AnnounceResponseCallback;
import org.slf4j.Logger;
import java.util.*;
import java.util.concurrent.*;
import static org.slf4j.LoggerFactory.getLogger;
@Slf4j
public class AnnouncerExecutor {
private static final Logger logger = getLogger(AnnouncerExecutor.class);
private final AnnounceResponseCallback announceResponseCallback;
private final ThreadPoolExecutor executorService;
@ -72,7 +71,7 @@ public class AnnouncerExecutor {
}
public List<Announcer> denyAll() {
final Set<InfoHash> infoHashes = Sets.newHashSet(this.currentlyRunning.keySet());
final Set<InfoHash> infoHashes = new HashSet<>(this.currentlyRunning.keySet());
final List<Announcer> announcersCanceled = new ArrayList<>();
for (final InfoHash infoHash: infoHashes) {
@ -92,26 +91,14 @@ public class AnnouncerExecutor {
try {
this.executorService.awaitTermination(10, TimeUnit.SECONDS);
} catch (final InterruptedException e) {
logger.warn("AnnouncerExecutor has ended with timeout, some torrents was still trying to announce after 10s", e);
log.warn("AnnouncerExecutor has ended with timeout, some torrents was still trying to announce after 10s", e);
}
}
@RequiredArgsConstructor
@Getter
private static final class AnnouncerWithFuture {
private final Announcer announcer;
private final Future<?> future;
private AnnouncerWithFuture(final Announcer announcer, final Future<?> future) {
this.announcer = announcer;
this.future = future;
}
public Announcer getAnnouncer() {
return announcer;
}
public Future<?> getFuture() {
return future;
}
}
}

View file

@ -1,26 +1,13 @@
package org.araymond.joal.core.ttorrent.client.announcer.request;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public class SuccessAnnounceResponse {
private final int interval;
private final int seeders;
private final int leechers;
public SuccessAnnounceResponse(final int interval, final int seeders, final int leechers) {
this.interval = interval;
this.seeders = seeders;
this.leechers = leechers;
}
public int getInterval() {
return interval;
}
public int getSeeders() {
return seeders;
}
public int getLeechers() {
return leechers;
}
}

View file

@ -1,6 +1,8 @@
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.events.announce.FailedToAnnounceEvent;
import org.araymond.joal.core.events.announce.SuccessfullyAnnounceEvent;
import org.araymond.joal.core.events.announce.TooManyAnnouncesFailedEvent;
@ -8,80 +10,58 @@ 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.request.SuccessAnnounceResponse;
import org.slf4j.Logger;
import org.springframework.context.ApplicationEventPublisher;
import static org.slf4j.LoggerFactory.getLogger;
@RequiredArgsConstructor
@Slf4j
public class AnnounceEventPublisher implements AnnounceResponseHandlerChainElement {
private static final Logger logger = getLogger(AnnounceEventPublisher.class);
private final ApplicationEventPublisher eventPublisher;
public AnnounceEventPublisher(final ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Override
public void onAnnouncerWillAnnounce(final Announcer announcer, final RequestEvent event) {
if(logger.isDebugEnabled()) {
logger.debug("Publish WillAnnounceEvent event for {}.", announcer.getTorrentInfoHash().humanReadableValue());
}
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) {
if(logger.isDebugEnabled()) {
logger.debug("Publish SuccessfullyAnnounceEvent event for {}.", announcer.getTorrentInfoHash().humanReadableValue());
}
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) {
if(logger.isDebugEnabled()) {
logger.debug("Publish FailedToAnnounceEvent event for {}.", announcer.getTorrentInfoHash().humanReadableValue());
}
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) {
if(logger.isDebugEnabled()) {
logger.debug("Publish SuccessfullyAnnounceEvent event for {}.", announcer.getTorrentInfoHash().humanReadableValue());
}
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) {
if(logger.isDebugEnabled()) {
logger.debug("Publish FailedToAnnounceEvent event for {}.", announcer.getTorrentInfoHash().humanReadableValue());
}
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) {
if(logger.isDebugEnabled()) {
logger.debug("Publish SuccessfullyAnnounceEvent event for {}.", announcer.getTorrentInfoHash().humanReadableValue());
}
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) {
if(logger.isDebugEnabled()) {
logger.debug("Publish FailedToAnnounceEvent event for {}.", announcer.getTorrentInfoHash().humanReadableValue());
}
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) {
if(logger.isDebugEnabled()) {
logger.debug("Publish TooManyAnnouncesFailedEvent event for {}.", announcer.getTorrentInfoHash().humanReadableValue());
}
log.debug("Publish TooManyAnnouncesFailedEvent event for {}.", announcer.getTorrentInfoHash().getHumanReadable());
this.eventPublisher.publishEvent(new TooManyAnnouncesFailedEvent(announcer));
}

View file

@ -1,59 +1,46 @@
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.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.request.AnnounceRequest;
import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse;
import org.slf4j.Logger;
import java.time.temporal.ChronoUnit;
import static org.slf4j.LoggerFactory.getLogger;
@RequiredArgsConstructor
@Slf4j
public class AnnounceReEnqueuer implements AnnounceResponseHandlerChainElement {
private static final Logger logger = getLogger(AnnounceReEnqueuer.class);
private final DelayQueue<AnnounceRequest> delayQueue;
public AnnounceReEnqueuer(final DelayQueue<AnnounceRequest> delayQueue) {
this.delayQueue = delayQueue;
}
@Override
public void onAnnouncerWillAnnounce(final Announcer announcer, final RequestEvent event) {
}
@Override
public void onAnnounceStartSuccess(final Announcer announcer, final SuccessAnnounceResponse result) {
if (logger.isDebugEnabled()) {
logger.debug("Enqueue torrent {} in regular queue.", announcer.getTorrentInfoHash().humanReadableValue());
}
log.debug("Enqueue torrent {} in regular queue.", announcer.getTorrentInfoHash().getHumanReadable());
this.delayQueue.addOrReplace(AnnounceRequest.createRegular(announcer), result.getInterval(), ChronoUnit.SECONDS);
}
@Override
public void onAnnounceStartFails(final Announcer announcer, final Throwable throwable) {
if (logger.isDebugEnabled()) {
logger.debug("Enqueue torrent {} in start queue once again (because it failed).", announcer.getTorrentInfoHash().humanReadableValue());
}
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);
}
@Override
public void onAnnounceRegularSuccess(final Announcer announcer, final SuccessAnnounceResponse result) {
if (logger.isDebugEnabled()) {
logger.debug("Enqueue torrent {} in regular queue.", announcer.getTorrentInfoHash().humanReadableValue());
}
log.debug("Enqueue torrent {} in regular queue.", announcer.getTorrentInfoHash().getHumanReadable());
this.delayQueue.addOrReplace(AnnounceRequest.createRegular(announcer), result.getInterval(), ChronoUnit.SECONDS);
}
@Override
public void onAnnounceRegularFails(final Announcer announcer, final Throwable throwable) {
if (logger.isDebugEnabled()) {
logger.debug("Enqueue torrent {} in regular queue once again (because it failed).", announcer.getTorrentInfoHash().humanReadableValue());
}
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);
}
@ -63,9 +50,7 @@ public class AnnounceReEnqueuer implements AnnounceResponseHandlerChainElement {
@Override
public void onAnnounceStopFails(final Announcer announcer, final Throwable throwable) {
if (logger.isDebugEnabled()) {
logger.debug("Enqueue torrent {} in stop queue once again (because it failed).", announcer.getTorrentInfoHash().humanReadableValue());
}
log.debug("Enqueue torrent {} in stop queue once again (because it failed).", announcer.getTorrentInfoHash().getHumanReadable());
this.delayQueue.addOrReplace(AnnounceRequest.createStop(announcer), 0, ChronoUnit.SECONDS);
}

View file

@ -6,8 +6,8 @@ import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnoun
import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse;
public interface AnnounceResponseCallback {
void onAnnounceWillAnnounce(final RequestEvent event, final Announcer announcer);
void onAnnounceSuccess(final RequestEvent event, final Announcer announcer, final SuccessAnnounceResponse result);
void onAnnounceFailure(final RequestEvent event, final Announcer announcer, final Throwable throwable);
void onTooManyAnnounceFailedInARaw(final RequestEvent event, final Announcer announcer, final TooMuchAnnouncesFailedInARawException e);
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);
}

View file

@ -1,18 +1,16 @@
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.request.SuccessAnnounceResponse;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.List;
import static org.slf4j.LoggerFactory.getLogger;
@Slf4j
public class AnnounceResponseHandlerChain implements AnnounceResponseCallback {
private static final Logger logger = getLogger(AnnounceResponseHandlerChain.class);
private final List<AnnounceResponseHandlerChainElement> chainElements;
public AnnounceResponseHandlerChain() {
@ -47,7 +45,7 @@ public class AnnounceResponseHandlerChain implements AnnounceResponseCallback {
break;
}
default: {
logger.warn("Event {} cannot be handled by {}", event.getEventName(), getClass().getSimpleName());
log.warn("Event {} cannot be handled by {}", event.getEventName(), getClass().getSimpleName());
break;
}
}
@ -71,7 +69,7 @@ public class AnnounceResponseHandlerChain implements AnnounceResponseCallback {
break;
}
default: {
logger.warn("Event {} cannot be handled by {}", event.getEventName(), getClass().getSimpleName());
log.warn("Event {} cannot be handled by {}", event.getEventName(), getClass().getSimpleName());
break;
}
}

View file

@ -6,12 +6,12 @@ import org.araymond.joal.core.ttorrent.client.announcer.exceptions.TooMuchAnnoun
import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse;
public interface AnnounceResponseHandlerChainElement {
void onAnnouncerWillAnnounce(final Announcer announcer, RequestEvent event);
void onAnnounceStartSuccess(final Announcer announcer, final SuccessAnnounceResponse result);
void onAnnounceStartFails(final Announcer announcer, final Throwable throwable);
void onAnnounceRegularSuccess(final Announcer announcer, final SuccessAnnounceResponse result);
void onAnnounceRegularFails(final Announcer announcer, final Throwable throwable);
void onAnnounceStopSuccess(final Announcer announcer, final SuccessAnnounceResponse result);
void onAnnounceStopFails(final Announcer announcer, final Throwable throwable);
void onTooManyAnnounceFailedInARaw(final Announcer announcer, final TooMuchAnnouncesFailedInARawException e);
void onAnnouncerWillAnnounce(Announcer announcer, RequestEvent event);
void onAnnounceStartSuccess(Announcer announcer, SuccessAnnounceResponse result);
void onAnnounceStartFails(Announcer announcer, Throwable throwable);
void onAnnounceRegularSuccess(Announcer announcer, SuccessAnnounceResponse result);
void onAnnounceRegularFails(Announcer announcer, Throwable throwable);
void onAnnounceStopSuccess(Announcer announcer, SuccessAnnounceResponse result);
void onAnnounceStopFails(Announcer announcer, Throwable throwable);
void onTooManyAnnounceFailedInARaw(Announcer announcer, TooMuchAnnouncesFailedInARawException e);
}

View file

@ -1,32 +1,26 @@
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.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.request.SuccessAnnounceResponse;
import org.slf4j.Logger;
import static org.slf4j.LoggerFactory.getLogger;
@RequiredArgsConstructor
@Slf4j
public class BandwidthDispatcherNotifier implements AnnounceResponseHandlerChainElement {
private static final Logger logger = getLogger(BandwidthDispatcherNotifier.class);
private final BandwidthDispatcher bandwidthDispatcher;
public BandwidthDispatcherNotifier(final BandwidthDispatcher bandwidthDispatcher) {
this.bandwidthDispatcher = bandwidthDispatcher;
}
@Override
public void onAnnouncerWillAnnounce(final Announcer announcer, final RequestEvent event) {
}
@Override
public void onAnnounceStartSuccess(final Announcer announcer, final SuccessAnnounceResponse result) {
if(logger.isDebugEnabled()) {
logger.debug("Register {} in bandwidth dispatcher and update stats.", announcer.getTorrentInfoHash().humanReadableValue());
}
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());
@ -38,9 +32,7 @@ public class BandwidthDispatcherNotifier implements AnnounceResponseHandlerChain
@Override
public void onAnnounceRegularSuccess(final Announcer announcer, final SuccessAnnounceResponse result) {
if(logger.isDebugEnabled()) {
logger.debug("Update {} stats in bandwidth dispatcher.", announcer.getTorrentInfoHash().humanReadableValue());
}
log.debug("Update {} stats in bandwidth dispatcher.", announcer.getTorrentInfoHash().getHumanReadable());
final InfoHash infoHash = announcer.getTorrentInfoHash();
this.bandwidthDispatcher.updateTorrentPeers(infoHash, result.getSeeders(), result.getLeechers());
}
@ -51,9 +43,7 @@ public class BandwidthDispatcherNotifier implements AnnounceResponseHandlerChain
@Override
public void onAnnounceStopSuccess(final Announcer announcer, final SuccessAnnounceResponse result) {
if(logger.isDebugEnabled()) {
logger.debug("Unregister {} from bandwidth dispatcher.", announcer.getTorrentInfoHash().humanReadableValue());
}
log.debug("Unregister {} from bandwidth dispatcher.", announcer.getTorrentInfoHash().getHumanReadable());
this.bandwidthDispatcher.unregisterTorrent(announcer.getTorrentInfoHash());
}
@ -63,9 +53,7 @@ public class BandwidthDispatcherNotifier implements AnnounceResponseHandlerChain
@Override
public void onTooManyAnnounceFailedInARaw(final Announcer announcer, final TooMuchAnnouncesFailedInARawException e) {
if(logger.isDebugEnabled()) {
logger.debug("Unregister {} from bandwidth dispatcher.", announcer.getTorrentInfoHash().humanReadableValue());
}
log.debug("Unregister {} from bandwidth dispatcher.", announcer.getTorrentInfoHash().getHumanReadable());
this.bandwidthDispatcher.unregisterTorrent(announcer.getTorrentInfoHash());
}
}

View file

@ -1,16 +1,14 @@
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.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.request.SuccessAnnounceResponse;
import org.slf4j.Logger;
import static org.slf4j.LoggerFactory.getLogger;
@Slf4j
public class ClientNotifier implements AnnounceResponseHandlerChainElement {
private static final Logger logger = getLogger(ClientNotifier.class);
private Client client;
public void setClient(final Client client) {
@ -45,9 +43,7 @@ public class ClientNotifier implements AnnounceResponseHandlerChainElement {
@Override
public void onAnnounceStopSuccess(final Announcer announcer, final SuccessAnnounceResponse result) {
if(logger.isDebugEnabled()) {
logger.debug("Notify client that a torrent has stopped.");
}
log.debug("Notify client that a torrent has stopped.");
this.client.onTorrentHasStopped(announcer);
}
@ -57,9 +53,7 @@ public class ClientNotifier implements AnnounceResponseHandlerChainElement {
@Override
public void onTooManyAnnounceFailedInARaw(final Announcer announcer, final TooMuchAnnouncesFailedInARawException e) {
if(logger.isDebugEnabled()) {
logger.debug("Notify client that a torrent has failed too many times.");
}
log.debug("Notify client that a torrent has failed too many times.");
this.client.onTooManyFailedInARaw(announcer);
}
}

View file

@ -10,14 +10,9 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.fluent.Response;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.araymond.joal.core.ttorrent.client.announcer.request.SuccessAnnounceResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
@ -25,8 +20,6 @@ import java.net.URI;
import java.util.Map;
public class TrackerClient {
private static final Logger logger = LoggerFactory.getLogger(TrackerClient.class);
private final TrackerClientUriProvider trackerClientUriProvider;
private final HttpClient httpClient;
private final ResponseHandler<TrackerMessage> trackerResponseHandler;
@ -73,8 +66,7 @@ public class TrackerClient {
final AnnounceResponseMessage announceResponseMessage = (AnnounceResponseMessage) responseMessage;
final int interval = announceResponseMessage.getInterval();
// Subtract one to seeders since we are one of them.
final int seeders = announceResponseMessage.getComplete() == 0 ? 0 : announceResponseMessage.getComplete() - 1;
final int seeders = announceResponseMessage.getComplete() == 0 ? 0 : announceResponseMessage.getComplete() - 1; // Subtract one to seeders since we are one of them
final int leechers = announceResponseMessage.getIncomplete();
return new SuccessAnnounceResponse(interval, seeders, leechers);
}

View file

@ -1,7 +1,6 @@
package org.araymond.joal.core.ttorrent.client.announcer.tracker;
import com.google.common.collect.Iterators;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
@ -9,8 +8,6 @@ import java.util.Iterator;
import java.util.List;
public class TrackerClientUriProvider {
private static final Logger logger = LoggerFactory.getLogger(TrackerClientUriProvider.class);
private final Iterator<URI> addressIterator;
private URI currentURI = null;

View file

@ -3,20 +3,17 @@ package org.araymond.joal.core.ttorrent.client.announcer.tracker;
import com.turn.ttorrent.client.announce.AnnounceException;
import com.turn.ttorrent.common.protocol.TrackerMessage;
import com.turn.ttorrent.common.protocol.http.HTTPTrackerMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ResponseHandler;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.ByteBuffer;
import static org.slf4j.LoggerFactory.getLogger;
@Slf4j
public class TrackerResponseHandler implements ResponseHandler<TrackerMessage> {
private static final Logger logger = getLogger(TrackerResponseHandler.class);
@Override
public TrackerMessage handleResponse(final HttpResponse response) throws IOException {
final HttpEntity entity = response.getEntity();
@ -26,9 +23,9 @@ public class TrackerResponseHandler implements ResponseHandler<TrackerMessage> {
}
final int contentLength = entity.getContentLength() < 1 ? 1024 : (int) entity.getContentLength();
try(final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(contentLength)) {
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(contentLength)) {
if (response.getStatusLine().getStatusCode() >= 300) {
logger.warn("Tracker response is an error.");
log.warn("Tracker response is an error.");
}
try {

View file

@ -11,7 +11,6 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@ -37,7 +36,7 @@ public class JacksonConfig {
/**
* Forces the timezone to be added to every LocalDateTime
*/
public static final class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
public static final class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(final LocalDateTime value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException {
final ZonedDateTime zonedDateTime = value.atZone(ZoneId.systemDefault());

View file

@ -1,13 +1,9 @@
package org.araymond.joal.web.config;
import org.araymond.joal.web.annotations.ConditionalOnWebUi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.*;
@ConditionalOnWebUi
// Do not use @EnableWebMvc as it will remove all the default springboot config.

View file

@ -1,7 +1,7 @@
package org.araymond.joal.web.config.obfuscation;
import lombok.extern.slf4j.Slf4j;
import org.araymond.joal.web.annotations.ConditionalOnWebUi;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@ -11,16 +11,14 @@ import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import static org.slf4j.LoggerFactory.getLogger;
/**
* As a security measure, all request coming from a non-prefixed URI will be closed before an actual response is sent to the client.
*/
@ConditionalOnWebUi
@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AbortNonPrefixedRequestFilter implements Filter {
private static final Logger logger = getLogger(AbortNonPrefixedRequestFilter.class);
private final String pathPrefix;
public AbortNonPrefixedRequestFilter(@Value("${joal.ui.path.prefix}")final String pathPrefix) {
@ -36,7 +34,7 @@ public class AbortNonPrefixedRequestFilter implements Filter {
requestedUri = requestedUri.substring(1);
}
if (!requestedUri.startsWith(pathPrefix)) {
logger.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

@ -2,23 +2,13 @@ package org.araymond.joal.web.config.obfuscation;
import org.araymond.joal.web.annotations.ConditionalOnWebUi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Created by raymo on 25/07/2017.
*/
@ -43,6 +33,4 @@ public class EndpointObfuscatorConfiguration {
servletRegistrationBean.setName("joal");
return servletRegistrationBean;
}
}

View file

@ -2,17 +2,23 @@ package org.araymond.joal.web.config.security;
import org.araymond.joal.web.annotations.ConditionalOnWebUi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
/**
* Created by raymo on 29/07/2017.
*/
@ConditionalOnWebUi
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public class WebSecurityConfig {
private final String pathPrefix;
private final boolean shouldDisableFrameOptions;
@ -24,18 +30,27 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
this.shouldDisableFrameOptions = shouldDisableFrameOptions;
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
if (this.shouldDisableFrameOptions) {
http.headers().frameOptions().disable();
}
http
return http
.httpBasic().disable()
.formLogin().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/" + this.pathPrefix).permitAll()
.antMatchers("/" + this.pathPrefix + "/ui/**").permitAll()
.anyRequest().denyAll();
.anyRequest().denyAll()
.and().build();
}
// Provide an empty UserDetailService to prevent spring from injecting a default one with a valid random password.
@Bean
public InMemoryUserDetailsManager userDetailsService() {
return new InMemoryUserDetailsManager();
}
}

View file

@ -45,5 +45,4 @@ public class AuthChannelInterceptorAdapter implements ChannelInterceptor {
return message;
}
}

View file

@ -45,5 +45,4 @@ public class WebSocketAuthenticatorService {
Collections.singleton((GrantedAuthority) () -> "USER")
);
}
}

View file

@ -2,12 +2,13 @@ package org.araymond.joal.web.messages.incoming.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
/**
* Created by raymo on 23/07/2017.
*/
@Getter
public class Base64TorrentIncomingMessage {
private final String fileName;
private final String b64String;
@ -16,12 +17,4 @@ public class Base64TorrentIncomingMessage {
this.fileName = fileName;
this.b64String = b64String;
}
public String getFileName() {
return fileName;
}
public String getB64String() {
return b64String;
}
}

View file

@ -2,13 +2,16 @@ package org.araymond.joal.web.messages.incoming.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects;
import lombok.Getter;
import lombok.ToString;
import org.araymond.joal.core.config.AppConfiguration;
import org.araymond.joal.core.config.AppConfigurationIntegrityException;
/**
* Created by raymo on 09/07/2017.
*/
@Getter
@ToString
public class ConfigIncomingMessage {
private final Long minUploadRate;
private final Long maxUploadRate;
@ -31,38 +34,7 @@ public class ConfigIncomingMessage {
this.keepTorrentWithZeroLeechers = keepTorrentWithZeroLeechers;
}
public Long getMinUploadRate() {
return minUploadRate;
}
public Long getMaxUploadRate() {
return maxUploadRate;
}
public Integer getSimultaneousSeed() {
return simultaneousSeed;
}
public String getClient() {
return client;
}
public boolean shouldKeepTorrentWithZeroLeechers() {
return keepTorrentWithZeroLeechers;
}
public AppConfiguration toAppConfiguration() throws AppConfigurationIntegrityException {
return new AppConfiguration(this.minUploadRate, this.maxUploadRate, this.simultaneousSeed, this.client, keepTorrentWithZeroLeechers);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("minUploadRate", minUploadRate)
.add("maxUploadRate", maxUploadRate)
.add("simultaneousSeed", simultaneousSeed)
.add("client", client)
.add("keepTorrentWithZeroLeechers", keepTorrentWithZeroLeechers)
.toString();
}
}

View file

@ -2,10 +2,12 @@ package org.araymond.joal.web.messages.outgoing;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import lombok.Getter;
/**
* Created by raymo on 29/06/2017.
*/
@Getter
public class StompMessage {
private final StompMessageTypes type;
@ -23,12 +25,4 @@ public class StompMessage {
public static StompMessage wrap(final MessagePayload payload) {
return new StompMessage(StompMessageTypes.typeFor(payload), payload);
}
public StompMessageTypes getType() {
return type;
}
public MessagePayload getPayload() {
return payload;
}
}

View file

@ -1,11 +1,13 @@
package org.araymond.joal.web.messages.outgoing.impl.announce;
import lombok.Getter;
import org.araymond.joal.core.torrent.torrent.InfoHash;
import org.araymond.joal.core.ttorrent.client.announcer.AnnouncerFacade;
import org.araymond.joal.web.messages.outgoing.MessagePayload;
import java.time.LocalDateTime;
@Getter
public abstract class AnnouncePayload implements MessagePayload {
private final InfoHash infoHash;
private final String torrentName;
@ -26,29 +28,4 @@ public abstract class AnnouncePayload implements MessagePayload {
this.lastKnownLeechers = announcerFacade.getLastKnownLeechers().orElse(null);
this.lastKnownSeeders = announcerFacade.getLastKnownSeeders().orElse(null);
}
public InfoHash getInfoHash() {
return infoHash;
}
public String getTorrentName() {
return torrentName;
}
public long getTorrentSize() {
return torrentSize;
}
public int getLastKnownInterval() {
return lastKnownInterval;
}
public int getConsecutiveFails() {
return consecutiveFails;
}
public LocalDateTime getLastAnnouncedAt() {
return lastAnnouncedAt;
}
public Integer getLastKnownLeechers() {
return lastKnownLeechers;
}
public Integer getLastKnownSeeders() {
return lastKnownSeeders;
}
}

View file

@ -1,7 +1,9 @@
package org.araymond.joal.web.messages.outgoing.impl.announce;
import lombok.Getter;
import org.araymond.joal.core.events.announce.FailedToAnnounceEvent;
@Getter
public class FailedToAnnouncePayload extends AnnouncePayload {
private final String errMessage;
@ -9,8 +11,4 @@ public class FailedToAnnouncePayload extends AnnouncePayload {
super(event.getAnnouncerFacade());
this.errMessage = event.getErrMessage();
}
public String getErrMessage() {
return errMessage;
}
}

View file

@ -1,8 +1,10 @@
package org.araymond.joal.web.messages.outgoing.impl.announce;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;
import lombok.Getter;
import org.araymond.joal.core.events.announce.SuccessfullyAnnounceEvent;
@Getter
public class SuccessfullyAnnouncePayload extends AnnouncePayload {
private final RequestEvent requestEvent;
@ -10,8 +12,4 @@ public class SuccessfullyAnnouncePayload extends AnnouncePayload {
super(event.getAnnouncerFacade());
this.requestEvent = event.getEvent();
}
public RequestEvent getRequestEvent() {
return requestEvent;
}
}

View file

@ -1,5 +1,6 @@
package org.araymond.joal.web.messages.outgoing.impl.config;
import lombok.Getter;
import org.araymond.joal.core.config.AppConfiguration;
import org.araymond.joal.core.events.config.ConfigHasBeenLoadedEvent;
import org.araymond.joal.web.messages.outgoing.MessagePayload;
@ -7,14 +8,11 @@ import org.araymond.joal.web.messages.outgoing.MessagePayload;
/**
* Created by raymo on 08/07/2017.
*/
@Getter
public class ConfigHasBeenLoadedPayload implements MessagePayload {
private final AppConfiguration config;
public ConfigHasBeenLoadedPayload(final ConfigHasBeenLoadedEvent event) {
this.config = event.getConfiguration();
}
public AppConfiguration getConfig() {
return config;
}
}

View file

@ -1,5 +1,6 @@
package org.araymond.joal.web.messages.outgoing.impl.config;
import lombok.Getter;
import org.araymond.joal.core.config.AppConfiguration;
import org.araymond.joal.core.events.config.ConfigurationIsInDirtyStateEvent;
import org.araymond.joal.web.messages.outgoing.MessagePayload;
@ -7,14 +8,11 @@ import org.araymond.joal.web.messages.outgoing.MessagePayload;
/**
* Created by raymo on 09/07/2017.
*/
@Getter
public class ConfigIsInDirtyStatePayload implements MessagePayload {
private final AppConfiguration config;
public ConfigIsInDirtyStatePayload(final ConfigurationIsInDirtyStateEvent event) {
this.config = event.getConfiguration();
}
public AppConfiguration getConfig() {
return config;
}
}

View file

@ -1,18 +1,16 @@
package org.araymond.joal.web.messages.outgoing.impl.config;
import lombok.Getter;
import org.araymond.joal.web.messages.outgoing.MessagePayload;
/**
* Created by raymo on 08/07/2017.
*/
@Getter
public class InvalidConfigPayload implements MessagePayload {
private final String error;
public InvalidConfigPayload(final Exception e) {
this.error = e.getMessage();
}
public String getError() {
return error;
}
}

View file

@ -1,5 +1,6 @@
package org.araymond.joal.web.messages.outgoing.impl.config;
import lombok.Getter;
import org.araymond.joal.core.events.config.ListOfClientFilesEvent;
import org.araymond.joal.web.messages.outgoing.MessagePayload;
@ -8,14 +9,11 @@ import java.util.List;
/**
* Created by raymo on 08/07/2017.
*/
@Getter
public class ListOfClientFilesPayload implements MessagePayload {
private final List<String> clients;
public ListOfClientFilesPayload(final ListOfClientFilesEvent event) {
this.clients = event.getClients();
}
public List<String> getClients() {
return clients;
}
}

View file

@ -1,11 +1,13 @@
package org.araymond.joal.web.messages.outgoing.impl.files;
import lombok.Getter;
import org.araymond.joal.core.events.torrent.files.FailedToAddTorrentFileEvent;
import org.araymond.joal.web.messages.outgoing.MessagePayload;
/**
* Created by raymo on 10/07/2017.
*/
@Getter
public class FailedToAddTorrentFilePayload implements MessagePayload {
private final String fileName;
private final String error;
@ -14,12 +16,4 @@ public class FailedToAddTorrentFilePayload implements MessagePayload {
this.fileName = event.getFileName();
this.error = event.getError();
}
public String getFileName() {
return fileName;
}
public String getError() {
return error;
}
}

View file

@ -1,5 +1,6 @@
package org.araymond.joal.web.messages.outgoing.impl.files;
import lombok.Getter;
import org.araymond.joal.core.events.torrent.files.TorrentFileAddedEvent;
import org.araymond.joal.core.torrent.torrent.InfoHash;
import org.araymond.joal.web.messages.outgoing.MessagePayload;
@ -7,6 +8,7 @@ import org.araymond.joal.web.messages.outgoing.MessagePayload;
/**
* Created by raymo on 10/07/2017.
*/
@Getter
public class TorrentFileAddedPayload implements MessagePayload {
private final InfoHash infoHash;
private final String name;
@ -17,16 +19,4 @@ public class TorrentFileAddedPayload implements MessagePayload {
this.name = event.getTorrent().getName();
this.size = event.getTorrent().getSize();
}
public InfoHash getInfoHash() {
return infoHash;
}
public String getName() {
return name;
}
public Long getSize() {
return size;
}
}

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