Compare commits

...

2 commits

11 changed files with 117 additions and 53 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; import org.araymond.joalcore.annotations.ddd.ValueObject;

View file

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

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; 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 org.araymond.joalcore.core.sharing.domain.Contribution;
import java.util.Optional; import java.util.Optional;
public interface PersistentStats { public interface OverallContributions {
Optional<Contribution> overallContributions(InfoHash infoHash); Optional<Contribution> load(InfoHash infoHash);
/* /*
TODO: implementation should have the while map in memory. a call to persistOverallContribution update the map. 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 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; 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.metadata.domain.TorrentMetadata;
import org.araymond.joalcore.core.sharing.application.exceptions.UnknownSharedTorrentException; import org.araymond.joalcore.core.sharing.application.exceptions.UnknownSharedTorrentException;
import org.araymond.joalcore.core.sharing.domain.*; import org.araymond.joalcore.core.sharing.domain.*;
import org.araymond.joalcore.core.sharing.domain.events.TorrentCreatedEvent; 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.DomainEvent;
import org.araymond.joalcore.events.DomainEventPublisher; import org.araymond.joalcore.events.DomainEventPublisher;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -13,35 +12,27 @@ import org.springframework.stereotype.Component;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
@Component @Component
public class SharedTorrentService { public class SharedTorrentService {
private final SharedTorrentRepository torrents; private final SharedTorrentRepository torrents;
private final DomainEventPublisher publisher; private final DomainEventPublisher publisher;
private final Supplier<PeerElection> electionSupplier; private final OverallContributions overallContributions;
private final PersistentStats persistentStats; private final SharedTorrentConfig config;
@Autowired @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.torrents = torrents;
this.publisher = publisher; this.publisher = publisher;
this.electionSupplier = electionSupplier; this.overallContributions = overallContributions;
this.persistentStats = persistentStats; this.config = config;
} }
public void create(TorrentMetadata metadata) { public void create(TorrentMetadata metadata) {
var events = new ArrayList<DomainEvent>(); var events = new ArrayList<DomainEvent>();
var overallContributions = persistentStats.overallContributions(metadata.infoHash()).orElse(Contribution.ZERO); var overallContributions = loadOverallContribution(metadata);
var left = new Left(Math.max(metadata.size().bytes() - overallContributions.downloaded().bytes(), 0));
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()));
}
*/
var torrent = new SharedTorrent(metadata.infoHash(), overallContributions, left); var torrent = new SharedTorrent(metadata.infoHash(), overallContributions, left);
events.add(new TorrentCreatedEvent(torrent.id())); events.add(new TorrentCreatedEvent(torrent.id()));
@ -54,10 +45,24 @@ public class SharedTorrentService {
publish(events); publish(events);
} }
private Contribution loadOverallContribution(TorrentMetadata metadata) {
return 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);
return overall;
});
}
public void registerPeers(InfoHash infoHash, Swarm.TrackerUniqueIdentifier identifier, Peers peers) { 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 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); torrents.save(torrent);
publisher.publish(events); publisher.publish(events);
@ -86,7 +91,7 @@ public class SharedTorrentService {
torrent.add(upload); torrent.add(upload);
torrents.save(torrent); torrents.save(torrent);
persistentStats.persistOverallContribution(torrent.infoHash(), torrent.overallContributions()); overallContributions.save(torrent.infoHash(), torrent.overallContributions());
} }
public void addDownload(SharedTorrentId id, DownloadAmount download) { public void addDownload(SharedTorrentId id, DownloadAmount download) {
@ -96,7 +101,7 @@ public class SharedTorrentService {
torrents.save(torrent); torrents.save(torrent);
publish(events); publish(events);
persistentStats.persistOverallContribution(torrent.infoHash(), torrent.overallContributions()); overallContributions.save(torrent.infoHash(), torrent.overallContributions());
} }
private void publish(List<DomainEvent> events) { private void publish(List<DomainEvent> events) {

View file

@ -1,7 +1,7 @@
package org.araymond.joalcore.core.sharing.domain; package org.araymond.joalcore.core.sharing.domain;
import org.araymond.joalcore.annotations.ddd.AggregateRoot; 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.DoneDownloadingEvent;
import org.araymond.joalcore.core.sharing.domain.events.TorrentPausedEvent; import org.araymond.joalcore.core.sharing.domain.events.TorrentPausedEvent;
import org.araymond.joalcore.core.sharing.domain.events.TorrentPeersChangedEvent; import org.araymond.joalcore.core.sharing.domain.events.TorrentPeersChangedEvent;

View file

@ -1,6 +1,6 @@
package org.araymond.joalcore.core.sharing.domain; 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; import java.util.Optional;

View file

@ -1,6 +1,6 @@
package org.araymond.joalcore.core.fixtures; 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 org.araymond.joalcore.core.sharing.domain.*;
import java.util.Random; 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; import org.junit.jupiter.api.Test;

View file

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