introduce config "skipDownload" in SharedServiceTorrent to be able to start a new torrent in seed only (skipping the download phase)

This commit is contained in:
Anthony RAYMOND 2024-08-17 01:27:14 +02:00
parent c346e61a12
commit a55c8d0e10
11 changed files with 113 additions and 52 deletions

View file

@ -1,4 +1,4 @@
package org.araymond.joalcore.core.infohash.domain;
package org.araymond.joalcore.core.metadata.domain;
import org.araymond.joalcore.annotations.ddd.ValueObject;

View file

@ -1,21 +1,10 @@
package org.araymond.joalcore.core.metadata.domain;
import org.araymond.joalcore.core.infohash.domain.InfoHash;
import java.util.Objects;
public class TorrentMetadata {
private final InfoHash infoHash;
private final long totalSize;
public TorrentMetadata(InfoHash infoHash, long totalSize) {
this.infoHash = infoHash;
this.totalSize = totalSize;
}
public InfoHash infoHash() {
return infoHash;
}
public long totalSize() {
return totalSize;
public record TorrentMetadata(InfoHash infoHash, TorrentSize size) {
public TorrentMetadata {
Objects.requireNonNull(infoHash, "TorrentMetadata requires a non-null [infoHash]");
Objects.requireNonNull(size, "TorrentMetadata requires a non-null [size]");
}
}

View file

@ -0,0 +1,43 @@
package org.araymond.joalcore.core.metadata.domain;
import org.araymond.joalcore.annotations.ddd.ValueObject;
import java.util.Objects;
@ValueObject
public final class TorrentSize {
private final long bytes;
public static TorrentSize ofBytes(long bytes) {
return new TorrentSize(bytes);
}
private TorrentSize(long bytes) {
this.bytes = bytes;
}
public long bytes() {
return bytes;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (TorrentSize) obj;
return this.bytes == that.bytes;
}
@Override
public int hashCode() {
return Objects.hash(bytes);
}
@Override
public String toString() {
return "TorrentSize[" +
"bytes=" + bytes + ']';
}
}

View file

@ -1,16 +1,16 @@
package org.araymond.joalcore.core.sharing.application;
import org.araymond.joalcore.core.infohash.domain.InfoHash;
import org.araymond.joalcore.core.metadata.domain.InfoHash;
import org.araymond.joalcore.core.sharing.domain.Contribution;
import java.util.Optional;
public interface PersistentStats {
Optional<Contribution> overallContributions(InfoHash infoHash);
public interface OverallContributions {
Optional<Contribution> load(InfoHash infoHash);
/*
TODO: implementation should have the while map in memory. a call to persistOverallContribution update the map.
Once in a while, the map is wrote to the disk asynchronously
*/
void persistOverallContribution(InfoHash infoHash, Contribution contribution);
void save(InfoHash infoHash, Contribution contribution);
}

View file

@ -0,0 +1,23 @@
package org.araymond.joalcore.core.sharing.application;
import org.araymond.joalcore.core.sharing.domain.services.PeerElection;
import java.util.function.Supplier;
public class SharedTorrentConfig {
private final Supplier<PeerElection> peersElection;
private final Supplier<Boolean> skipDownload;
public SharedTorrentConfig(Supplier<PeerElection> peersElection, Supplier<Boolean> skipDownload) {
this.peersElection = peersElection;
this.skipDownload = skipDownload;
}
public Supplier<PeerElection> peersElection() {
return peersElection;
}
public Supplier<Boolean> skipDownload() {
return skipDownload;
}
}

View file

@ -1,11 +1,10 @@
package org.araymond.joalcore.core.sharing.application;
import org.araymond.joalcore.core.infohash.domain.InfoHash;
import org.araymond.joalcore.core.metadata.domain.InfoHash;
import org.araymond.joalcore.core.metadata.domain.TorrentMetadata;
import org.araymond.joalcore.core.sharing.application.exceptions.UnknownSharedTorrentException;
import org.araymond.joalcore.core.sharing.domain.*;
import org.araymond.joalcore.core.sharing.domain.events.TorrentCreatedEvent;
import org.araymond.joalcore.core.sharing.domain.services.PeerElection;
import org.araymond.joalcore.events.DomainEvent;
import org.araymond.joalcore.events.DomainEventPublisher;
import org.springframework.beans.factory.annotation.Autowired;
@ -13,35 +12,38 @@ import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
@Component
public class SharedTorrentService {
private final SharedTorrentRepository torrents;
private final DomainEventPublisher publisher;
private final Supplier<PeerElection> electionSupplier;
private final PersistentStats persistentStats;
private final OverallContributions overallContributions;
private final SharedTorrentConfig config;
@Autowired
public SharedTorrentService(SharedTorrentRepository torrents, DomainEventPublisher publisher, Supplier<PeerElection> electionSupplier, PersistentStats persistentStats) {
public SharedTorrentService(SharedTorrentRepository torrents, DomainEventPublisher publisher, OverallContributions overallContributions, SharedTorrentConfig config) {
this.torrents = torrents;
this.publisher = publisher;
this.electionSupplier = electionSupplier;
this.persistentStats = persistentStats;
this.overallContributions = overallContributions;
this.config = config;
}
public void create(TorrentMetadata metadata) {
var events = new ArrayList<DomainEvent>();
var overallContributions = persistentStats.overallContributions(metadata.infoHash()).orElse(Contribution.ZERO);
var overallContributions = this.overallContributions.load(metadata.infoHash())
.orElseGet(() -> {
Contribution overall = Contribution.ZERO;
if (config.skipDownload().get()) {
// return a fully Downloaded contribution when the torrent is not yet known and skip download is true
overall = new Contribution(new DownloadAmount(metadata.size().bytes()), new UploadAmount(0));
}
this.overallContributions.save(metadata.infoHash(), overall);
var left = new Left(Math.max(metadata.totalSize() - overallContributions.downloaded().bytes(), 0));
/* TODO:
if (!config.downloadTorrentFirst()) {
left = 0;
saveTorrentStats(new Contribution(metadata.totalSize(), overallContribs.uploaded()));
}
*/
return overall;
});
var left = new Left(Math.max(metadata.size().bytes() - overallContributions.downloaded().bytes(), 0));
var torrent = new SharedTorrent(metadata.infoHash(), overallContributions, left);
events.add(new TorrentCreatedEvent(torrent.id()));
@ -57,7 +59,7 @@ public class SharedTorrentService {
public void registerPeers(InfoHash infoHash, Swarm.TrackerUniqueIdentifier identifier, Peers peers) {
var torrent = torrents.findByTorrentInfoHash(infoHash).orElseThrow(() -> new UnknownSharedTorrentException("No torrent found for %s".formatted(infoHash)));
var events = torrent.registerPeers(identifier, peers, electionSupplier.get());
var events = torrent.registerPeers(identifier, peers, config.peersElection().get());
torrents.save(torrent);
publisher.publish(events);
@ -86,7 +88,7 @@ public class SharedTorrentService {
torrent.add(upload);
torrents.save(torrent);
persistentStats.persistOverallContribution(torrent.infoHash(), torrent.overallContributions());
overallContributions.save(torrent.infoHash(), torrent.overallContributions());
}
public void addDownload(SharedTorrentId id, DownloadAmount download) {
@ -96,7 +98,7 @@ public class SharedTorrentService {
torrents.save(torrent);
publish(events);
persistentStats.persistOverallContribution(torrent.infoHash(), torrent.overallContributions());
overallContributions.save(torrent.infoHash(), torrent.overallContributions());
}
private void publish(List<DomainEvent> events) {

View file

@ -1,7 +1,7 @@
package org.araymond.joalcore.core.sharing.domain;
import org.araymond.joalcore.annotations.ddd.AggregateRoot;
import org.araymond.joalcore.core.infohash.domain.InfoHash;
import org.araymond.joalcore.core.metadata.domain.InfoHash;
import org.araymond.joalcore.core.sharing.domain.events.DoneDownloadingEvent;
import org.araymond.joalcore.core.sharing.domain.events.TorrentPausedEvent;
import org.araymond.joalcore.core.sharing.domain.events.TorrentPeersChangedEvent;

View file

@ -1,6 +1,6 @@
package org.araymond.joalcore.core.sharing.domain;
import org.araymond.joalcore.core.infohash.domain.InfoHash;
import org.araymond.joalcore.core.metadata.domain.InfoHash;
import java.util.Optional;

View file

@ -1,6 +1,6 @@
package org.araymond.joalcore.core.fixtures;
import org.araymond.joalcore.core.infohash.domain.InfoHash;
import org.araymond.joalcore.core.metadata.domain.InfoHash;
import org.araymond.joalcore.core.sharing.domain.*;
import java.util.Random;

View file

@ -1,4 +1,4 @@
package org.araymond.joalcore.core.infohash.domain;
package org.araymond.joalcore.core.metadata.domain;
import org.junit.jupiter.api.Test;

View file

@ -1,8 +1,9 @@
package org.araymond.joalcore.core.sharing.application;
import org.araymond.joalcore.core.fixtures.TestFixtures;
import org.araymond.joalcore.core.infohash.domain.InfoHash;
import org.araymond.joalcore.core.metadata.domain.InfoHash;
import org.araymond.joalcore.core.metadata.domain.TorrentMetadata;
import org.araymond.joalcore.core.metadata.domain.TorrentSize;
import org.araymond.joalcore.core.sharing.domain.*;
import org.araymond.joalcore.core.sharing.domain.events.*;
import org.araymond.joalcore.core.sharing.domain.services.PeerElection;
@ -18,17 +19,20 @@ import static org.assertj.core.api.Assertions.assertThat;
class SharedTorrentServiceTest {
private InMemorySharedTorrentRepository repo;
private FakePublisher publisher;
private PersistentStats persistentStats;
private OverallContributions overallContributions;
@BeforeEach
public void setUp() {
this.repo = new InMemorySharedTorrentRepository();
this.publisher = new FakePublisher();
this.persistentStats = new ZeroPersistentStats();
this.overallContributions = new ZeroOverallContributions();
}
public SharedTorrentService newService() {
return new SharedTorrentService(repo, publisher, () -> PeerElection.MOST_LEECHED, persistentStats);
return new SharedTorrentService(repo, publisher, overallContributions, new SharedTorrentConfig(
() -> PeerElection.MOST_LEECHED,
() -> false
));
}
@Test
@ -36,7 +40,7 @@ class SharedTorrentServiceTest {
var service = newService();
var infoHash = TestFixtures.randomInfoHash();
service.create(new TorrentMetadata(infoHash, 5000));
service.create(new TorrentMetadata(infoHash, TorrentSize.ofBytes(5000)));
assertThat(publisher.events).hasSizeGreaterThanOrEqualTo(1).first().isInstanceOf(TorrentCreatedEvent.class);
var torrentId = ((TorrentCreatedEvent) publisher.events.getFirst()).sharedTorrentId();
@ -74,14 +78,14 @@ class SharedTorrentServiceTest {
}
}
private static final class ZeroPersistentStats implements PersistentStats {
private static final class ZeroOverallContributions implements OverallContributions {
@Override
public Optional<Contribution> overallContributions(InfoHash infoHash) {
public Optional<Contribution> load(InfoHash infoHash) {
return Optional.empty();
}
@Override
public void persistOverallContribution(InfoHash infoHash, Contribution contribution) {
public void save(InfoHash infoHash, Contribution contribution) {
}
}