From dd94b478e294d5e7e5f5d9839881d90595cdab42 Mon Sep 17 00:00:00 2001 From: Anthony RAYMOND Date: Tue, 24 Oct 2023 23:13:01 +0200 Subject: [PATCH 1/5] stop torrent when target ratio is reached --- .../org/araymond/joal/core/SeedManager.java | 2 +- .../joal/core/config/AppConfiguration.java | 9 ++++- .../joal/core/ttorrent/client/Client.java | 5 +++ .../ttorrent/client/announcer/Announcer.java | 14 +++++++- .../client/announcer/AnnouncerFactory.java | 4 ++- .../request/AnnounceDataAccessor.java | 4 +++ .../announcer/response/ClientNotifier.java | 4 +++ .../config/ConfigIncomingMessage.java | 7 ++-- .../AppConfigurationSerializationTest.java | 4 +-- .../core/config/AppConfigurationTest.java | 34 +++++++++---------- .../core/config/JoalConfigProviderTest.java | 6 ++-- .../announcer/AnnouncerFactoryTest.java | 3 +- .../client/announcer/AnnouncerTest.java | 18 +++++----- src/test/resources/configtest/config.json | 3 +- 14 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/araymond/joal/core/SeedManager.java b/src/main/java/org/araymond/joal/core/SeedManager.java index a057805c..d1373b09 100644 --- a/src/main/java/org/araymond/joal/core/SeedManager.java +++ b/src/main/java/org/araymond/joal/core/SeedManager.java @@ -132,7 +132,7 @@ public void startSeeding() throws IOException { .withAppConfiguration(appConfig) .withTorrentFileProvider(this.torrentFileProvider) .withBandwidthDispatcher(this.bandwidthDispatcher) - .withAnnouncerFactory(new AnnouncerFactory(announceDataAccessor, httpClient)) + .withAnnouncerFactory(new AnnouncerFactory(announceDataAccessor, httpClient, appConfig)) .withEventPublisher(this.appEventPublisher) .withDelayQueue(new DelayQueue<>()) .build(); diff --git a/src/main/java/org/araymond/joal/core/config/AppConfiguration.java b/src/main/java/org/araymond/joal/core/config/AppConfiguration.java index bdab53b7..463dbcfd 100644 --- a/src/main/java/org/araymond/joal/core/config/AppConfiguration.java +++ b/src/main/java/org/araymond/joal/core/config/AppConfiguration.java @@ -20,6 +20,7 @@ public class AppConfiguration { private final int simultaneousSeed; private final String client; private final boolean keepTorrentWithZeroLeechers; + private final float uploadRatioTarget; @JsonCreator public AppConfiguration( @@ -27,13 +28,15 @@ public AppConfiguration( @JsonProperty(value = "maxUploadRate", required = true) final long maxUploadRate, @JsonProperty(value = "simultaneousSeed", required = true) final int simultaneousSeed, @JsonProperty(value = "client", required = true) final String client, - @JsonProperty(value = "keepTorrentWithZeroLeechers", required = true) final boolean keepTorrentWithZeroLeechers + @JsonProperty(value = "keepTorrentWithZeroLeechers", required = true) final boolean keepTorrentWithZeroLeechers, + @JsonProperty(value = "uploadRatioTarget", defaultValue = "-1.0", required = false) final float uploadRatioTarget ) { this.minUploadRate = minUploadRate; this.maxUploadRate = maxUploadRate; this.simultaneousSeed = simultaneousSeed; this.client = client; this.keepTorrentWithZeroLeechers = keepTorrentWithZeroLeechers; + this.uploadRatioTarget = uploadRatioTarget; validate(); } @@ -56,5 +59,9 @@ private void validate() { if (StringUtils.isBlank(client)) { throw new AppConfigurationIntegrityException("client is required, no file name given"); } + + if (uploadRatioTarget < 0f && uploadRatioTarget != -1f){ + throw new AppConfigurationIntegrityException("uploadRatioTarget must be greater than 0 (or equal to -1)"); + } } } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java b/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java index 2c087bbf..3305da4f 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java @@ -181,6 +181,10 @@ public void onNoMorePeers(final InfoHash infoHash) { } } + public void onUploadRatioLimitReached(final InfoHash infoHash) { + this.torrentFileProvider.moveToArchiveFolder(infoHash); + } + public void onTorrentHasStopped(final Announcer stoppedAnnouncer) { if (this.stop) { this.currentlySeedingAnnouncers.remove(stoppedAnnouncer); @@ -242,4 +246,5 @@ public List getCurrentlySeedingAnnouncers() { lock.unlock(); } } + } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/Announcer.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/Announcer.java index 872c32c7..5b60cf9e 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/Announcer.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/Announcer.java @@ -35,11 +35,14 @@ public class Announcer implements AnnouncerFacade { @Getter private final MockedTorrent torrent; private TrackerClient trackerClient; private final AnnounceDataAccessor announceDataAccessor; + private long reportedUploadBytes = 0L; + private final float uploadRatioTarget; - Announcer(final MockedTorrent torrent, final AnnounceDataAccessor announceDataAccessor, final HttpClient httpClient) { + Announcer(final MockedTorrent torrent, final AnnounceDataAccessor announceDataAccessor, final HttpClient httpClient, final float uploadRatioTarget) { this.torrent = torrent; this.trackerClient = this.buildTrackerClient(torrent, httpClient); this.announceDataAccessor = announceDataAccessor; + this.uploadRatioTarget = uploadRatioTarget; } private TrackerClient buildTrackerClient(final MockedTorrent torrent, HttpClient httpClient) { @@ -67,6 +70,7 @@ public SuccessAnnounceResponse announce(final RequestEvent event) throws Announc log.info("{} has announced successfully. Response: {} seeders, {} leechers, {}s interval", this.torrent.getTorrentInfoHash().getHumanReadable(), responseMessage.getSeeders(), responseMessage.getLeechers(), responseMessage.getInterval()); + this.reportedUploadBytes = announceDataAccessor.getUploaded(this.torrent.getTorrentInfoHash()); this.lastKnownInterval = responseMessage.getInterval(); this.lastKnownLeechers = responseMessage.getLeechers(); this.lastKnownSeeders = responseMessage.getSeeders(); @@ -116,6 +120,14 @@ public InfoHash getTorrentInfoHash() { return this.getTorrent().getTorrentInfoHash(); } + public boolean hasReachedUploadRatioLimit() { + if (uploadRatioTarget == -1f) { + return false; + } + final float bytesToUploadTarget = (uploadRatioTarget * (float) this.getTorrentSize()); + return reportedUploadBytes >= bytesToUploadTarget; + } + /** * Make sure to keep {@code torrentInfoHash} as the only input. */ diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactory.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactory.java index dddb8322..2a4ce01e 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactory.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactory.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.apache.http.client.HttpClient; +import org.araymond.joal.core.config.AppConfiguration; import org.araymond.joal.core.torrent.torrent.MockedTorrent; import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceDataAccessor; @@ -9,8 +10,9 @@ public class AnnouncerFactory { private final AnnounceDataAccessor announceDataAccessor; private final HttpClient httpClient; + private final AppConfiguration appConfiguration; public Announcer create(final MockedTorrent torrent) { - return new Announcer(torrent, this.announceDataAccessor, httpClient); + return new Announcer(torrent, this.announceDataAccessor, httpClient, appConfiguration.getUploadRatioTarget()); } } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessor.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessor.java index ec2dc98e..dc0199f2 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessor.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/request/AnnounceDataAccessor.java @@ -24,4 +24,8 @@ public String getHttpRequestQueryForTorrent(final InfoHash infoHash, final Reque public Set> getHttpHeadersForTorrent() { return this.bitTorrentClient.getHeaders(); } + + public long getUploaded(final InfoHash infoHash) { + return this.bandwidthDispatcher.getSeedStatForTorrent(infoHash).getUploaded(); + } } diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifier.java b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifier.java index f4deaf94..adac29f7 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifier.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/announcer/response/ClientNotifier.java @@ -34,6 +34,10 @@ public void onAnnounceStartFails(final Announcer announcer, final Throwable thro public void onAnnounceRegularSuccess(final Announcer announcer, final SuccessAnnounceResponse result) { if (result.getSeeders() < 1 || result.getLeechers() < 1) { this.client.onNoMorePeers(announcer.getTorrentInfoHash()); + return; + } + if (announcer.hasReachedUploadRatioLimit()) { + this.client.onUploadRatioLimitReached(announcer.getTorrentInfoHash()); } } diff --git a/src/main/java/org/araymond/joal/web/messages/incoming/config/ConfigIncomingMessage.java b/src/main/java/org/araymond/joal/web/messages/incoming/config/ConfigIncomingMessage.java index ed195310..12ba7a33 100644 --- a/src/main/java/org/araymond/joal/web/messages/incoming/config/ConfigIncomingMessage.java +++ b/src/main/java/org/araymond/joal/web/messages/incoming/config/ConfigIncomingMessage.java @@ -18,6 +18,7 @@ public class ConfigIncomingMessage { private final Integer simultaneousSeed; private final String client; private final boolean keepTorrentWithZeroLeechers; + private final Float uploadRatioTarget; @JsonCreator ConfigIncomingMessage( @@ -25,16 +26,18 @@ public class ConfigIncomingMessage { @JsonProperty(value = "maxUploadRate", required = true) final Long maxUploadRate, @JsonProperty(value = "simultaneousSeed", required = true) final Integer simultaneousSeed, @JsonProperty(value = "client", required = true) final String client, - @JsonProperty(value = "keepTorrentWithZeroLeechers", required = true) final boolean keepTorrentWithZeroLeechers + @JsonProperty(value = "keepTorrentWithZeroLeechers", required = true) final boolean keepTorrentWithZeroLeechers, + @JsonProperty(value = "uploadRatioTarget", defaultValue = "-1.0", required = false) final Float uploadRatioTarget ) { this.minUploadRate = minUploadRate; this.maxUploadRate = maxUploadRate; this.simultaneousSeed = simultaneousSeed; this.client = client; this.keepTorrentWithZeroLeechers = keepTorrentWithZeroLeechers; + this.uploadRatioTarget = uploadRatioTarget; } public AppConfiguration toAppConfiguration() throws AppConfigurationIntegrityException { - return new AppConfiguration(this.minUploadRate, this.maxUploadRate, this.simultaneousSeed, this.client, keepTorrentWithZeroLeechers); + return new AppConfiguration(this.minUploadRate, this.maxUploadRate, this.simultaneousSeed, this.client, keepTorrentWithZeroLeechers, this.uploadRatioTarget); } } diff --git a/src/test/java/org/araymond/joal/core/config/AppConfigurationSerializationTest.java b/src/test/java/org/araymond/joal/core/config/AppConfigurationSerializationTest.java index 767aae6f..c7e6f779 100644 --- a/src/test/java/org/araymond/joal/core/config/AppConfigurationSerializationTest.java +++ b/src/test/java/org/araymond/joal/core/config/AppConfigurationSerializationTest.java @@ -54,8 +54,8 @@ public void shouldFailToDeserializeIfKeepTorrentWithZeroLeechersIsNotDefined() t @Test public void shouldSerialize() throws JsonProcessingException { - final AppConfiguration config = new AppConfiguration(180L, 190L, 2, "azureus.client", false); - assertThat(mapper.writeValueAsString(config)).isEqualTo("{\"minUploadRate\":180,\"maxUploadRate\":190,\"simultaneousSeed\":2,\"client\":\"azureus.client\",\"keepTorrentWithZeroLeechers\":false}"); + final AppConfiguration config = new AppConfiguration(180L, 190L, 2, "azureus.client", false, 1f); + assertThat(mapper.writeValueAsString(config)).isEqualTo("{\"minUploadRate\":180,\"maxUploadRate\":190,\"simultaneousSeed\":2,\"client\":\"azureus.client\",\"keepTorrentWithZeroLeechers\":false,\"uploadRatioTarget\":1.0}"); } @Test diff --git a/src/test/java/org/araymond/joal/core/config/AppConfigurationTest.java b/src/test/java/org/araymond/joal/core/config/AppConfigurationTest.java index b638a7e5..fece0fdf 100644 --- a/src/test/java/org/araymond/joal/core/config/AppConfigurationTest.java +++ b/src/test/java/org/araymond/joal/core/config/AppConfigurationTest.java @@ -11,40 +11,40 @@ public class AppConfigurationTest { public static AppConfiguration createOne() { - return new AppConfiguration(30L, 150L, 2, "azureus", true); + return new AppConfiguration(30L, 150L, 2, "azureus", true, 1f); } @Test public void shouldNotBuildIfMinUploadRateIsLessThanZero() { - assertThatThrownBy(() -> new AppConfiguration(-1L, 190L, 2, "azureus.client", false)) + assertThatThrownBy(() -> new AppConfiguration(-1L, 190L, 2, "azureus.client", false, 1f)) .isInstanceOf(AppConfigurationIntegrityException.class) .hasMessageContaining("minUploadRate must be at least 0"); } @Test public void shouldBuildIfMinUploadRateEqualsZero() { - final AppConfiguration config = new AppConfiguration(0L, 190L, 2, "azureus.client", false); + final AppConfiguration config = new AppConfiguration(0L, 190L, 2, "azureus.client", false, 1f); assertThat(config.getMinUploadRate()).isEqualTo(0); } @Test public void shouldBuildIfMinUploadRateEqualsOne() { - final AppConfiguration config = new AppConfiguration(0L, 1L, 2, "azureus.client", false); + final AppConfiguration config = new AppConfiguration(0L, 1L, 2, "azureus.client", false, 1f); assertThat(config.getMaxUploadRate()).isEqualTo(1); } @Test public void shouldNotBuildIfMaxUploadRateIsLessThanZero() { - assertThatThrownBy(() -> new AppConfiguration(180L, -1L, 2, "azureus.client", false)) + assertThatThrownBy(() -> new AppConfiguration(180L, -1L, 2, "azureus.client", false, 1f)) .isInstanceOf(AppConfigurationIntegrityException.class) .hasMessageContaining("maxUploadRate must greater or equal to 0"); } @Test public void shouldBuildIfMinRateAndMaxRateEqualsZero() { - final AppConfiguration conf = new AppConfiguration(0L, 0L, 2, "azureus.client", false); + final AppConfiguration conf = new AppConfiguration(0L, 0L, 2, "azureus.client", false, 1f); assertThat(conf.getMinUploadRate()).isEqualTo(0L); assertThat(conf.getMaxUploadRate()).isEqualTo(0L); @@ -52,14 +52,14 @@ public void shouldBuildIfMinRateAndMaxRateEqualsZero() { @Test public void shouldNotBuildIfMaxRateIsLesserThanMinRate() { - assertThatThrownBy(() -> new AppConfiguration(180L, 179L, 2, "azureus.client", false)) + assertThatThrownBy(() -> new AppConfiguration(180L, 179L, 2, "azureus.client", false, 1f)) .isInstanceOf(AppConfigurationIntegrityException.class) .hasMessageContaining("maxUploadRate must be greater or equal to minUploadRate"); } @Test public void shouldBuildIfMaxRateEqualsMinRate() { - final AppConfiguration conf = new AppConfiguration(180L, 180L, 2, "azureus.client", false); + final AppConfiguration conf = new AppConfiguration(180L, 180L, 2, "azureus.client", false, 1f); assertThat(conf.getMinUploadRate()).isEqualTo(180L); assertThat(conf.getMaxUploadRate()).isEqualTo(180L); @@ -67,35 +67,35 @@ public void shouldBuildIfMaxRateEqualsMinRate() { @Test public void shouldNotBuildIfSimultaneousSeedIsLessThanOne() { - assertThatThrownBy(() -> new AppConfiguration(180L, 190L, 0, "azureus.client", false)) + assertThatThrownBy(() -> new AppConfiguration(180L, 190L, 0, "azureus.client", false, 1f)) .isInstanceOf(AppConfigurationIntegrityException.class) .hasMessageContaining("simultaneousSeed must be greater than 0"); } @Test public void shouldCreateIfSimultaneousSeedIsOne() { - final AppConfiguration config = new AppConfiguration(180L, 190L, 1, "azureus.client", false); + final AppConfiguration config = new AppConfiguration(180L, 190L, 1, "azureus.client", false, 1f); assertThat(config.getSimultaneousSeed()).isEqualTo(1); } @Test public void shouldNotBuildIfClientIsNull() { - assertThatThrownBy(() -> new AppConfiguration(180L, 190L, 2, null, false)) + assertThatThrownBy(() -> new AppConfiguration(180L, 190L, 2, null, false, 1f)) .isInstanceOf(AppConfigurationIntegrityException.class) .hasMessageContaining("client is required, no file name given"); } @Test public void shouldNotBuildIfClientIsEmpty() { - assertThatThrownBy(() -> new AppConfiguration(180L, 190L, 2, " ", false)) + assertThatThrownBy(() -> new AppConfiguration(180L, 190L, 2, " ", false, 1f)) .isInstanceOf(AppConfigurationIntegrityException.class) .hasMessageContaining("client is required, no file name given"); } @Test public void shouldBuild() { - final AppConfiguration config = new AppConfiguration(180L, 190L, 2, "azureus.client", false); + final AppConfiguration config = new AppConfiguration(180L, 190L, 2, "azureus.client", false, 1f); assertThat(config.getMinUploadRate()).isEqualTo(180); assertThat(config.getMaxUploadRate()).isEqualTo(190); @@ -105,15 +105,15 @@ public void shouldBuild() { @Test public void shouldBeEqualsByProperties() { - final AppConfiguration config = new AppConfiguration(180L, 190L, 2, "azureus.client", false); - final AppConfiguration config2 = new AppConfiguration(180L, 190L, 2, "azureus.client", false); + final AppConfiguration config = new AppConfiguration(180L, 190L, 2, "azureus.client", false, 1f); + final AppConfiguration config2 = new AppConfiguration(180L, 190L, 2, "azureus.client", false, 1f); assertThat(config).isEqualTo(config2); } @Test public void shouldHaveSameHashCodeWithSameProperties() { - final AppConfiguration config = new AppConfiguration(180L, 190L, 2, "azureus.client", false); - final AppConfiguration config2 = new AppConfiguration(180L, 190L, 2, "azureus.client", false); + final AppConfiguration config = new AppConfiguration(180L, 190L, 2, "azureus.client", false, 1f); + final AppConfiguration config2 = new AppConfiguration(180L, 190L, 2, "azureus.client", false, 1f); assertThat(config.hashCode()).isEqualTo(config2.hashCode()); } diff --git a/src/test/java/org/araymond/joal/core/config/JoalConfigProviderTest.java b/src/test/java/org/araymond/joal/core/config/JoalConfigProviderTest.java index 6e611d51..ed0cab6e 100644 --- a/src/test/java/org/araymond/joal/core/config/JoalConfigProviderTest.java +++ b/src/test/java/org/araymond/joal/core/config/JoalConfigProviderTest.java @@ -32,7 +32,8 @@ public class JoalConfigProviderTest { 190L, 5, "azureus-5.7.5.0.client", - false + false, + 1f ); @Test @@ -108,7 +109,8 @@ public void shouldWriteConfigurationFile() throws IOException { rand.longs(201, 400).findFirst().getAsLong(), rand.ints(1, 5).findFirst().getAsInt(), RandomStringUtils.random(60), - false + false, + 1f ); provider.saveNewConf(newConf); diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java index e5fab7d0..d3439186 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java @@ -1,6 +1,7 @@ package org.araymond.joal.core.ttorrent.client.announcer; import org.apache.http.client.HttpClient; +import org.araymond.joal.core.config.AppConfiguration; import org.araymond.joal.core.torrent.torrent.MockedTorrent; import org.araymond.joal.core.ttorrent.client.announcer.request.AnnounceDataAccessor; import org.araymond.joal.core.ttorrent.client.announcer.tracker.NoMoreUriAvailableException; @@ -20,7 +21,7 @@ public class AnnouncerFactoryTest { @Test public void shouldCreate() { final AnnounceDataAccessor announceDataAccessor = mock(AnnounceDataAccessor.class); - final AnnouncerFactory announcerFactory = new AnnouncerFactory(announceDataAccessor, Mockito.mock(HttpClient.class)); + final AnnouncerFactory announcerFactory = new AnnouncerFactory(announceDataAccessor, Mockito.mock(HttpClient.class), mock(AppConfiguration.class)); final MockedTorrent torrent = mock(MockedTorrent.class); given(torrent.getAnnounceList()).willReturn(list(list(URI.create("http://localhost")))); diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerTest.java index 633364e1..f3b15802 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerTest.java @@ -28,7 +28,7 @@ public class AnnouncerTest { @Test public void shouldProvideRequiredInfoForAnnouncerFacade() { final MockedTorrent torrent = MockedTorrentTest.createOneMock(); - final AnnouncerFacade facade = new Announcer(torrent, null, Mockito.mock(HttpClient.class)); + final AnnouncerFacade facade = new Announcer(torrent, null, Mockito.mock(HttpClient.class), 1f); assertThat(facade.getConsecutiveFails()).isEqualTo(0); assertThat(facade.getLastKnownInterval()).isEqualTo(5); @@ -47,7 +47,7 @@ public void shouldThrowTooManyFailsExceptionIfFailsFiveTimesInARow() throws Anno doReturn("dd=ff&qq=d").when(dataAccessor).getHttpRequestQueryForTorrent(any(InfoHash.class), eq(RequestEvent.STARTED)); doReturn(new HashSet<>()).when(dataAccessor).getHttpHeadersForTorrent(); - final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class)); + final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class), 1f); announcer.setTrackerClient(trackerClient); //noinspection Duplicates @@ -80,7 +80,7 @@ public void shouldResetConsecutiveFailsOnAnnounceSuccess() throws AnnounceExcept doReturn("dd=ff&qq=d").when(dataAccessor).getHttpRequestQueryForTorrent(any(InfoHash.class), eq(RequestEvent.STARTED)); doReturn(new HashSet<>()).when(dataAccessor).getHttpHeadersForTorrent(); - final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class)); + final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class), 1f); announcer.setTrackerClient(trackerClient); //noinspection Duplicates @@ -116,7 +116,7 @@ public void shouldUpdateAnnounceDateOnEachAnnounce() throws AnnounceException, I doReturn("dd=ff&qq=d").when(dataAccessor).getHttpRequestQueryForTorrent(any(InfoHash.class), eq(RequestEvent.STARTED)); doReturn(new HashSet<>()).when(dataAccessor).getHttpHeadersForTorrent(); - final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class)); + final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class), 1f); announcer.setTrackerClient(trackerClient); assertThat(announcer.getLastAnnouncedAt()).isEmpty(); @@ -155,7 +155,7 @@ public void shouldUpdateLastPeersStatsAndIntevalOnEachAnnounce() throws Announce doReturn("dd=ff&qq=d").when(dataAccessor).getHttpRequestQueryForTorrent(any(InfoHash.class), eq(RequestEvent.STARTED)); doReturn(new HashSet<>()).when(dataAccessor).getHttpHeadersForTorrent(); - final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class)); + final Announcer announcer = new Announcer(torrent, dataAccessor, Mockito.mock(HttpClient.class), 1f); announcer.setTrackerClient(trackerClient); assertThat(announcer.getLastKnownInterval()).isEqualTo(5); @@ -194,11 +194,11 @@ public void shouldUpdateLastPeersStatsAndIntevalOnEachAnnounce() throws Announce @Test public void shouldBeEqualsByInfoHash() { final MockedTorrent torrent1 = MockedTorrentTest.createOneMock("abcd"); - final Announcer announcer1 = new Announcer(torrent1, null, Mockito.mock(HttpClient.class)); + final Announcer announcer1 = new Announcer(torrent1, null, Mockito.mock(HttpClient.class), 1f); final MockedTorrent torrent2 = MockedTorrentTest.createOneMock("abcd"); - final Announcer announcer2 = new Announcer(torrent2, null, Mockito.mock(HttpClient.class)); + final Announcer announcer2 = new Announcer(torrent2, null, Mockito.mock(HttpClient.class), 1f); assertThat(announcer1).isEqualTo(announcer2); } @@ -207,11 +207,11 @@ public void shouldBeEqualsByInfoHash() { @Test public void shouldNotBeEqualsWithDifferentInfoHash() { final MockedTorrent torrent1 = MockedTorrentTest.createOneMock("abcd"); - final Announcer announcer1 = new Announcer(torrent1, null, Mockito.mock(HttpClient.class)); + final Announcer announcer1 = new Announcer(torrent1, null, Mockito.mock(HttpClient.class), 1f); final MockedTorrent torrent2 = MockedTorrentTest.createOneMock("abcdefgh"); - final Announcer announcer2 = new Announcer(torrent2, null, Mockito.mock(HttpClient.class)); + final Announcer announcer2 = new Announcer(torrent2, null, Mockito.mock(HttpClient.class), 1f); assertThat(announcer1).isNotEqualTo(announcer2); } diff --git a/src/test/resources/configtest/config.json b/src/test/resources/configtest/config.json index 2061d899..f12ea23f 100644 --- a/src/test/resources/configtest/config.json +++ b/src/test/resources/configtest/config.json @@ -3,5 +3,6 @@ "maxUploadRate": 190, "simultaneousSeed": 5, "client": "azureus-5.7.5.0.client", - "keepTorrentWithZeroLeechers": false + "keepTorrentWithZeroLeechers": false, + "uploadRatioTarget": 1.0 } From 117486c51b0b480c81e7a811422d6ca796a25a5a Mon Sep 17 00:00:00 2001 From: Anthony RAYMOND Date: Tue, 24 Oct 2023 23:21:14 +0200 Subject: [PATCH 2/5] fix test build --- .../core/ttorrent/client/announcer/AnnouncerFactoryTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java index d3439186..fd71d2eb 100644 --- a/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java +++ b/src/test/java/org/araymond/joal/core/ttorrent/client/announcer/AnnouncerFactoryTest.java @@ -33,7 +33,7 @@ public void shouldCreate() { @Test public void createThrowsIfTorrentContainsNoValidURIs() { final AnnounceDataAccessor announceDataAccessor = mock(AnnounceDataAccessor.class); - final AnnouncerFactory announcerFactory = new AnnouncerFactory(announceDataAccessor, Mockito.mock(HttpClient.class)); + final AnnouncerFactory announcerFactory = new AnnouncerFactory(announceDataAccessor, Mockito.mock(HttpClient.class), mock(AppConfiguration.class)); assertThatThrownBy(() -> announcerFactory.create(mock(MockedTorrent.class))) .isInstanceOf(NoMoreUriAvailableException.class); From 3b3947b475ed7512dc5104786d4c17ac9e48d089 Mon Sep 17 00:00:00 2001 From: Anthony RAYMOND Date: Tue, 24 Oct 2023 23:23:06 +0200 Subject: [PATCH 3/5] Add log --- .../java/org/araymond/joal/core/ttorrent/client/Client.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java b/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java index 3305da4f..2b619edb 100644 --- a/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java +++ b/src/main/java/org/araymond/joal/core/ttorrent/client/Client.java @@ -3,6 +3,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent; +import lombok.extern.slf4j.Slf4j; import org.araymond.joal.core.config.AppConfiguration; import org.araymond.joal.core.events.torrent.files.TorrentFileAddedEvent; import org.araymond.joal.core.events.torrent.files.TorrentFileDeletedEvent; @@ -36,6 +37,7 @@ *
  • implements {@link TorrentFileChangeAware} to react to torrent file changes in filesystem
  • * */ +@Slf4j public class Client implements TorrentFileChangeAware, ClientFacade { private final AppConfiguration appConfig; private final TorrentFileProvider torrentFileProvider; @@ -182,6 +184,7 @@ public void onNoMorePeers(final InfoHash infoHash) { } public void onUploadRatioLimitReached(final InfoHash infoHash) { + log.info("Deleting torrent [{}] since ratio has been met", infoHash); this.torrentFileProvider.moveToArchiveFolder(infoHash); } From 13f1f9a7ad4af33950311a597470628fa74bf84d Mon Sep 17 00:00:00 2001 From: Anthony Raymond Date: Wed, 1 Nov 2023 11:07:13 +0100 Subject: [PATCH 4/5] Update config.json --- resources/config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/config.json b/resources/config.json index 7ba1eb89..57ac40b3 100644 --- a/resources/config.json +++ b/resources/config.json @@ -3,5 +3,6 @@ "maxUploadRate": 170, "simultaneousSeed": 5, "client": "utorrent-3.5.0_43916.client", - "keepTorrentWithZeroLeechers": true + "keepTorrentWithZeroLeechers": true, + "uploadRatioTarget": -1.0 } From a61797f53a8f5cfd10a7c44176c7d2d7762b0017 Mon Sep 17 00:00:00 2001 From: Anthony Raymond Date: Wed, 1 Nov 2023 11:08:54 +0100 Subject: [PATCH 5/5] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b60da7bb..a0a57be3 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,8 @@ The application configuration belongs in `joal-conf/config.json`. "maxUploadRate" : 160, "simultaneousSeed" : 20, "client" : "qbittorrent-3.3.16.client", - "keepTorrentWithZeroLeechers" : true + "keepTorrentWithZeroLeechers" : true, + "uploadRatioTarget": -1.0 } ``` - `minUploadRate` : The minimum uploadRate you want to fake (in kB/s) (**required**) @@ -109,6 +110,7 @@ The application configuration belongs in `joal-conf/config.json`. - `simultaneousSeed` : How many torrents should be seeding at the same time (**required**) - `client` : The name of the .client file to use in `joal-conf/clients/` (**required**) - `keepTorrentWithZeroLeechers`: should JOAL keep torrent with no leechers or seeders. If yes, torrent with no peers will be seed at 0kB/s. If false torrents will be deleted on 0 peers reached. (**required**) +- `uploadRatioTarget`: when JOAL has uploaded X times the size of the torrent **in a single session**, the torrent is removed. If -1.0 torrents are never removed.