mirror of
https://github.com/anthonyraymond/joal.git
synced 2024-09-20 07:16:26 +08:00
commit
eb5d35c8ef
|
@ -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
23
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,5 +13,4 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|||
public interface KeyAlgorithm {
|
||||
|
||||
String generate();
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -11,5 +11,4 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|||
public interface PeerIdAlgorithm {
|
||||
|
||||
String generate();
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,5 +9,4 @@ public class UnrecognizedClientPlaceholder extends RuntimeException {
|
|||
public UnrecognizedClientPlaceholder(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -18,8 +18,9 @@ public final class ClientBuilder {
|
|||
private DelayQueue<AnnounceRequest> delayQueue;
|
||||
|
||||
private ClientBuilder() {
|
||||
// private
|
||||
}
|
||||
|
||||
|
||||
public static ClientBuilder builder() {
|
||||
return new ClientBuilder();
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,5 +45,4 @@ public class AuthChannelInterceptorAdapter implements ChannelInterceptor {
|
|||
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,5 +45,4 @@ public class WebSocketAuthenticatorService {
|
|||
Collections.singleton((GrantedAuthority) () -> "USER")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue