From bb8ffd6eff21e0de715f22da3204419cc631ccdf Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 12 May 2024 22:24:34 +0800 Subject: [PATCH 01/33] add Sha1Hash splitHashes method --- .../jtorrent/data/torrent/model/BencodedInfo.java | 11 ----------- .../data/torrent/model/BencodedMultiFileInfo.java | 3 ++- .../torrent/model/BencodedSingleFileInfo.java | 2 +- .../jtorrent/domain/common/util/Sha1Hash.java | 15 +++++++++++++++ 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/main/java/jtorrent/data/torrent/model/BencodedInfo.java b/src/main/java/jtorrent/data/torrent/model/BencodedInfo.java index 224c0de4..a0de789f 100644 --- a/src/main/java/jtorrent/data/torrent/model/BencodedInfo.java +++ b/src/main/java/jtorrent/data/torrent/model/BencodedInfo.java @@ -2,7 +2,6 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -32,16 +31,6 @@ protected BencodedInfo(int pieceLength, byte[] pieces, String name) { this.name = name; } - protected List getDomainPieceHashes() { - List pieceHashes = new ArrayList<>(); - for (int i = 0; i < pieces.length; i += Sha1Hash.HASH_SIZE) { - byte[] pieceHash = new byte[Sha1Hash.HASH_SIZE]; - System.arraycopy(pieces, i, pieceHash, 0, Sha1Hash.HASH_SIZE); - pieceHashes.add(new Sha1Hash(pieceHash)); - } - return pieceHashes; - } - public int getNumPieces() { return pieces.length / Sha1Hash.HASH_SIZE; } diff --git a/src/main/java/jtorrent/data/torrent/model/BencodedMultiFileInfo.java b/src/main/java/jtorrent/data/torrent/model/BencodedMultiFileInfo.java index 4834c2b2..c0012653 100644 --- a/src/main/java/jtorrent/data/torrent/model/BencodedMultiFileInfo.java +++ b/src/main/java/jtorrent/data/torrent/model/BencodedMultiFileInfo.java @@ -61,7 +61,8 @@ public long getTotalSize() { @Override public FileInfo toDomain() { List fileMetaData = buildFileMetaData(); - return new MultiFileInfo(name, fileMetaData, pieceLength, getDomainPieceHashes(), new Sha1Hash(getInfoHash())); + return new MultiFileInfo(name, fileMetaData, pieceLength, Sha1Hash.splitHashes(pieces), + new Sha1Hash(getInfoHash())); } protected List buildFileMetaData() { diff --git a/src/main/java/jtorrent/data/torrent/model/BencodedSingleFileInfo.java b/src/main/java/jtorrent/data/torrent/model/BencodedSingleFileInfo.java index 607bc363..fc097925 100644 --- a/src/main/java/jtorrent/data/torrent/model/BencodedSingleFileInfo.java +++ b/src/main/java/jtorrent/data/torrent/model/BencodedSingleFileInfo.java @@ -51,7 +51,7 @@ public long getTotalSize() { @Override public FileInfo toDomain() { FileMetadata fileMetaData = buildFileMetaData(); - return new SingleFileInfo(fileMetaData, pieceLength, getDomainPieceHashes(), new Sha1Hash(getInfoHash())); + return new SingleFileInfo(fileMetaData, pieceLength, Sha1Hash.splitHashes(pieces), new Sha1Hash(getInfoHash())); } private FileMetadata buildFileMetaData() { diff --git a/src/main/java/jtorrent/domain/common/util/Sha1Hash.java b/src/main/java/jtorrent/domain/common/util/Sha1Hash.java index e46ccacf..395f420b 100644 --- a/src/main/java/jtorrent/domain/common/util/Sha1Hash.java +++ b/src/main/java/jtorrent/domain/common/util/Sha1Hash.java @@ -2,6 +2,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.List; public class Sha1Hash extends Bit160Value { @@ -49,6 +50,20 @@ public static Sha1Hash fromHexString(String hexString) { return new Sha1Hash(hash); } + public static List splitHashes(byte[] hashesConcat) { + if (hashesConcat.length % HASH_SIZE != 0) { + throw new IllegalArgumentException("Invalid concatenated hashes length"); + } + + List pieceHashes = new ArrayList<>(); + for (int i = 0; i < hashesConcat.length; i += Sha1Hash.HASH_SIZE) { + byte[] pieceHash = new byte[Sha1Hash.HASH_SIZE]; + System.arraycopy(hashesConcat, i, pieceHash, 0, Sha1Hash.HASH_SIZE); + pieceHashes.add(new Sha1Hash(pieceHash)); + } + return pieceHashes; + } + public static byte[] concatHashes(List hashes) { byte[] hashesConcat = new byte[hashes.size() * HASH_SIZE]; for (int i = 0; i < hashes.size(); i++) { From 1996ca1559221b17034ce3fdb2f155097c0c299d Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 12 May 2024 22:25:45 +0800 Subject: [PATCH 02/33] Implement RxObservableCollectionBase toString --- .../domain/common/util/rx/RxObservableCollectionBase.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/jtorrent/domain/common/util/rx/RxObservableCollectionBase.java b/src/main/java/jtorrent/domain/common/util/rx/RxObservableCollectionBase.java index 5dff745f..166475a2 100644 --- a/src/main/java/jtorrent/domain/common/util/rx/RxObservableCollectionBase.java +++ b/src/main/java/jtorrent/domain/common/util/rx/RxObservableCollectionBase.java @@ -95,4 +95,9 @@ public boolean equals(Object o) { RxObservableCollectionBase that = (RxObservableCollectionBase) o; return Objects.equals(collection, that.collection); } + + @Override + public String toString() { + return collection.toString(); + } } From 70a37bc4d7b1b31077d18a8a31cf5b579de4d1f8 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 12 May 2024 22:28:14 +0800 Subject: [PATCH 03/33] Ditch java 9 modules --- build.gradle | 14 -------------- src/main/java/module-info.java | 21 --------------------- 2 files changed, 35 deletions(-) delete mode 100644 src/main/java/module-info.java diff --git a/build.gradle b/build.gradle index cac24f0a..a390ccf0 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,6 @@ plugins { id 'application' id 'checkstyle' id 'org.openjfx.javafxplugin' version '0.0.14' - id 'org.gradlex.extra-java-module-info' version '1.4.1' } group 'jtorrent' @@ -13,13 +12,7 @@ sourceCompatibility = '17' targetCompatibility = '17' application { - mainModule = 'jtorrent' mainClass = 'jtorrent.Main' - // Workaround for the following error: - // java.lang.IllegalAccessError: class ch.qos.logback.core.boolex.JaninoEventEvaluatorBase - // (in module ch.qos.logback.core) cannot access class org.codehaus.janino.ScriptEvaluator - // (in module org.codehaus.janino) because module ch.qos.logback.core does not read module org.codehaus.janino - applicationDefaultJvmArgs = ['--add-reads', 'ch.qos.logback.core=org.codehaus.janino'] } repositories { @@ -48,13 +41,6 @@ dependencies { testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' } -extraJavaModuleInfo { - module('com.dampcake:bencode', 'com.dampcake.bencode', '1.4') { - exports('com.dampcake.bencode') - } -} - test { useJUnitPlatform() - jvmArgs = ['--add-reads', 'ch.qos.logback.core=org.codehaus.janino'] } \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java deleted file mode 100644 index 7cbe2780..00000000 --- a/src/main/java/module-info.java +++ /dev/null @@ -1,21 +0,0 @@ -module jtorrent { - requires java.desktop; - requires java.logging; - requires javafx.controls; - requires javafx.fxml; - requires io.reactivex.rxjava3; - requires com.dampcake.bencode; - requires atlantafx.base; - requires org.kordamp.ikonli.javafx; - requires org.kordamp.ikonli.materialdesign2; - requires org.slf4j; - - opens jtorrent.presentation to javafx.graphics; - opens jtorrent.presentation.common.component to javafx.fxml; - opens jtorrent.presentation.main.view to javafx.fxml; - opens jtorrent.presentation.addnewtorrent.view to javafx.fxml; - opens jtorrent.presentation.peerinput.view to javafx.fxml; - opens jtorrent.presentation.createnewtorrent.view to javafx.fxml; - opens jtorrent.presentation.exception.view to javafx.fxml; - opens jtorrent.presentation.preferences.view to javafx.fxml; -} From 714a88115c04d4918b17f0e604f736f2079a2a79 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 12 May 2024 22:29:42 +0800 Subject: [PATCH 04/33] Add hibernate ORM & h2 dependencies --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index a390ccf0..912ea318 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { id 'application' id 'checkstyle' id 'org.openjfx.javafxplugin' version '0.0.14' + id "org.hibernate.orm" version "6.5.0.Final" } group 'jtorrent' @@ -36,6 +37,7 @@ dependencies { implementation 'org.kordamp.ikonli:ikonli-materialdesign2-pack:12.3.1' implementation 'ch.qos.logback:logback-classic:1.5.6' implementation 'org.codehaus.janino:janino:3.1.12' + implementation 'com.h2database:h2:2.2.224' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' From 5b7451beba6c8769f0964abcf862e3afe5eb4150 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 12 May 2024 22:30:55 +0800 Subject: [PATCH 05/33] Add hibernate.properties --- src/main/resources/hibernate.properties | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/resources/hibernate.properties diff --git a/src/main/resources/hibernate.properties b/src/main/resources/hibernate.properties new file mode 100644 index 00000000..5bd9127c --- /dev/null +++ b/src/main/resources/hibernate.properties @@ -0,0 +1,12 @@ +# Database connection settings +hibernate.connection.url=jdbc:h2:file:file:./db;DB_CLOSE_DELAY=-1;AUTO_SERVER=TRUE +hibernate.connection.username=sa +hibernate.connection.password= + +# Echo all executed SQL to console +hibernate.show_sql=true +hibernate.format_sql=true +hibernate.highlight_sql=true + +# Automatically export the schema +hibernate.hbm2ddl.auto=update \ No newline at end of file From d97d7899843d37a1a0ebcdc4d364709903aa1c6f Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 12 May 2024 22:31:04 +0800 Subject: [PATCH 06/33] Remove jul.properties --- src/main/resources/jtorrent/jul.properties | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/main/resources/jtorrent/jul.properties diff --git a/src/main/resources/jtorrent/jul.properties b/src/main/resources/jtorrent/jul.properties deleted file mode 100644 index e5dcbd31..00000000 --- a/src/main/resources/jtorrent/jul.properties +++ /dev/null @@ -1,9 +0,0 @@ -handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler -.level=ALL -# ConsoleHandler configuration -java.util.logging.ConsoleHandler.level=ALL -java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter -# FileHandler configuration -java.util.logging.FileHandler.pattern=JTorrent.%u.%g.log -java.util.logging.FileHandler.level=ALL -java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter From 64cb8625d790398775037d7a7d6c9246aa17f492 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 12 May 2024 23:09:56 +0800 Subject: [PATCH 07/33] Implement database models & dao --- .../torrent/source/db/dao/TorrentDao.java | 57 +++++ .../source/db/model/FileInfoComponent.java | 144 +++++++++++++ .../db/model/FileMetadataComponent.java | 113 ++++++++++ .../db/model/FileProgressComponent.java | 46 ++++ .../source/db/model/TorrentEntity.java | 143 +++++++++++++ .../db/model/TorrentMetadataComponent.java | 96 +++++++++ .../db/model/TorrentProgressComponent.java | 85 ++++++++ .../db/model/TorrentStatisticsComponent.java | 46 ++++ .../torrent/source/db/util/HibernateUtil.java | 27 +++ src/main/java/jtorrent/domain/Client.java | 2 +- .../domain/torrent/model/FileInfo.java | 9 +- .../domain/torrent/model/FileProgress.java | 101 +++++++-- .../domain/torrent/model/MultiFileInfo.java | 7 +- .../domain/torrent/model/SingleFileInfo.java | 5 +- .../domain/torrent/model/Torrent.java | 86 ++++++-- .../domain/torrent/model/TorrentProgress.java | 199 ++++++++++++++---- .../torrent/model/TorrentStatistics.java | 53 ++++- 17 files changed, 1139 insertions(+), 80 deletions(-) create mode 100644 src/main/java/jtorrent/data/torrent/source/db/dao/TorrentDao.java create mode 100644 src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java create mode 100644 src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java create mode 100644 src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java create mode 100644 src/main/java/jtorrent/data/torrent/source/db/model/TorrentEntity.java create mode 100644 src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java create mode 100644 src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java create mode 100644 src/main/java/jtorrent/data/torrent/source/db/model/TorrentStatisticsComponent.java create mode 100644 src/main/java/jtorrent/data/torrent/source/db/util/HibernateUtil.java diff --git a/src/main/java/jtorrent/data/torrent/source/db/dao/TorrentDao.java b/src/main/java/jtorrent/data/torrent/source/db/dao/TorrentDao.java new file mode 100644 index 00000000..fa73843f --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/db/dao/TorrentDao.java @@ -0,0 +1,57 @@ +package jtorrent.data.torrent.source.db.dao; + +import java.util.Arrays; +import java.util.List; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; + +import jtorrent.data.torrent.source.db.model.TorrentEntity; +import jtorrent.data.torrent.source.db.util.HibernateUtil; + +public class TorrentDao { + + private final SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + + public void create(TorrentEntity torrentEntity) { + try (Session session = sessionFactory.openSession()) { + session.beginTransaction(); + session.persist(torrentEntity); + session.getTransaction().commit(); + } + } + + public List readAll() { + try (Session session = sessionFactory.openSession()) { + return session.createQuery("from TorrentEntity", TorrentEntity.class).list(); + } + } + + public TorrentEntity read(byte[] infoHash) { + try (Session session = sessionFactory.openSession()) { + TorrentEntity torrentEntity = session.get(TorrentEntity.class, infoHash); + if (torrentEntity == null) { + throw new IllegalArgumentException( + "TorrentEntity with infoHash " + Arrays.toString(infoHash) + " does not exist"); + } + return torrentEntity; + } + } + + public void update(TorrentEntity torrentEntity) { + try (Session session = sessionFactory.openSession()) { + session.beginTransaction(); + session.merge(torrentEntity); + session.getTransaction().commit(); + } + } + + public void delete(byte[] infoHash) { + try (Session session = sessionFactory.openSession()) { + session.beginTransaction(); + var entity = session.get(TorrentEntity.class, infoHash); + session.remove(entity); + session.getTransaction().commit(); + } + } +} diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java new file mode 100644 index 00000000..399b595d --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java @@ -0,0 +1,144 @@ +package jtorrent.data.torrent.source.db.model; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.hibernate.annotations.Formula; + +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embeddable; +import jakarta.persistence.FetchType; +import jakarta.persistence.OrderColumn; +import jtorrent.domain.common.util.Sha1Hash; +import jtorrent.domain.torrent.model.FileInfo; +import jtorrent.domain.torrent.model.FileMetadata; +import jtorrent.domain.torrent.model.MultiFileInfo; +import jtorrent.domain.torrent.model.SingleFileInfo; + +@Embeddable +public class FileInfoComponent { + + @Column + private final String directory; + + @OrderColumn + @ElementCollection + private final List fileMetadata; + + @OrderColumn + @ElementCollection(fetch = FetchType.EAGER) + private final List pieceHashes; + + @Column(nullable = false) + private final int pieceSize; + + @Formula("infoHash") + private final byte[] infoHash; + + protected FileInfoComponent() { + this(null, Collections.emptyList(), Collections.emptyList(), 0, null); + } + + public FileInfoComponent(String directory, List fileMetadata, List pieceHashes, + int pieceSize, byte[] infoHash) { + this.directory = directory; + this.fileMetadata = fileMetadata; + this.pieceHashes = pieceHashes; + this.pieceSize = pieceSize; + this.infoHash = infoHash; + } + + public static FileInfoComponent fromDomain(FileInfo fileInfo) { + if (fileInfo instanceof SingleFileInfo singleFileInfo) { + return fromDomain(singleFileInfo); + } else { + return fromDomain((MultiFileInfo) fileInfo); + } + } + + public static FileInfoComponent fromDomain(SingleFileInfo singleFileInfo) { + List fileMetadata = singleFileInfo.getFileMetaData().stream() + .map(FileMetadataComponent::fromDomain) + .toList(); + List pieceHashes = singleFileInfo.getPieceHashes().stream() + .map(Sha1Hash::getBytes) + .toList(); + int pieceSize = singleFileInfo.getPieceSize(); + byte[] infoHash = singleFileInfo.getInfoHash().getBytes(); + return new FileInfoComponent(null, fileMetadata, pieceHashes, pieceSize, infoHash); + } + + public static FileInfoComponent fromDomain(MultiFileInfo multiFileInfo) { + String directory = multiFileInfo.getDirectory(); + List fileMetadata = multiFileInfo.getFileMetaData().stream() + .map(FileMetadataComponent::fromDomain) + .toList(); + List pieceHashes = multiFileInfo.getPieceHashes().stream() + .map(Sha1Hash::getBytes) + .toList(); + int pieceSize = multiFileInfo.getPieceSize(); + byte[] infoHash = multiFileInfo.getInfoHash().getBytes(); + return new FileInfoComponent(directory, fileMetadata, pieceHashes, pieceSize, infoHash); + } + + public FileInfo toDomain() { + return isSingleFile() ? toSingleFileInfo() : toMultiFileInfo(); + } + + private boolean isSingleFile() { + return directory == null; + } + + private SingleFileInfo toSingleFileInfo() { + FileMetadata domainFileMetadata = fileMetadata.get(0).toDomain(); + List domainPieceHashes = pieceHashes.stream() + .map(Sha1Hash::new) + .toList(); + Sha1Hash domainInfoHash = new Sha1Hash(infoHash); + return new SingleFileInfo(domainFileMetadata, pieceSize, domainPieceHashes, domainInfoHash); + } + + private MultiFileInfo toMultiFileInfo() { + List domainFileMetadata = fileMetadata.stream() + .map(FileMetadataComponent::toDomain) + .toList(); + List domainPieceHashes = pieceHashes.stream() + .map(Sha1Hash::new) + .toList(); + Sha1Hash domainInfoHash = new Sha1Hash(infoHash); + return new MultiFileInfo(directory, domainFileMetadata, pieceSize, domainPieceHashes, domainInfoHash); + } + + public String getDirectory() { + return directory; + } + + public List getFileMetadata() { + return fileMetadata; + } + + public List getPieceHashes() { + return pieceHashes; + } + + public int getPieceSize() { + return pieceSize; + } + + public byte[] getInfoHash() { + return infoHash; + } + + @Override + public String toString() { + return "FileInfoComponent{" + + "directory='" + directory + '\'' + + ", fileMetadata=" + fileMetadata + + ", pieceHashes=" + pieceHashes.stream().map(Arrays::toString).toList() + + ", pieceSize=" + pieceSize + + ", infoHash=" + Arrays.toString(infoHash) + + '}'; + } +} diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java new file mode 100644 index 00000000..c0f5ecac --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java @@ -0,0 +1,113 @@ +package jtorrent.data.torrent.source.db.model; + +import java.nio.file.Path; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jtorrent.domain.torrent.model.FileMetadata; + +@Embeddable +public class FileMetadataComponent { + + @Column(nullable = false) + private final long size; + + @Column(nullable = false) + private final String path; + + @Column(nullable = false) + private final int firstPiece; + + @Column(nullable = false) + private final int firstPieceStart; + + @Column(nullable = false) + private final int lastPiece; + + @Column(nullable = false) + private final int lastPieceEnd; + + @Column(nullable = false) + private final long start; + + protected FileMetadataComponent() { + this(0, "", 0, 0, 0, 0, 0); + } + + public FileMetadataComponent(long size, String path, int firstPiece, int firstPieceStart, int lastPiece, + int lastPieceEnd, long start) { + this.size = size; + this.path = path; + this.firstPiece = firstPiece; + this.firstPieceStart = firstPieceStart; + this.lastPiece = lastPiece; + this.lastPieceEnd = lastPieceEnd; + this.start = start; + } + + public static FileMetadataComponent fromDomain(FileMetadata fileMetadata) { + return new FileMetadataComponent( + fileMetadata.size(), + fileMetadata.path().toString(), + fileMetadata.firstPiece(), + fileMetadata.firstPieceStart(), + fileMetadata.lastPiece(), + fileMetadata.lastPieceEnd(), + fileMetadata.start() + ); + } + + public FileMetadata toDomain() { + return new FileMetadata( + size, + Path.of(path), + firstPiece, + firstPieceStart, + lastPiece, + lastPieceEnd, + start, + start + size - 1 + ); + } + + public long getSize() { + return size; + } + + public String getPath() { + return path; + } + + public int getFirstPiece() { + return firstPiece; + } + + public int getFirstPieceStart() { + return firstPieceStart; + } + + public int getLastPiece() { + return lastPiece; + } + + public int getLastPieceEnd() { + return lastPieceEnd; + } + + public long getStart() { + return start; + } + + @Override + public String toString() { + return "FileMetadataComponent{" + + "size=" + size + + ", path='" + path + '\'' + + ", firstPiece=" + firstPiece + + ", firstPieceStart=" + firstPieceStart + + ", lastPiece=" + lastPiece + + ", lastPieceEnd=" + lastPieceEnd + + ", start=" + start + + '}'; + } +} diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java new file mode 100644 index 00000000..71cbcba5 --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java @@ -0,0 +1,46 @@ +package jtorrent.data.torrent.source.db.model; + +import java.util.Arrays; +import java.util.BitSet; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jtorrent.domain.torrent.model.FileInfo; +import jtorrent.domain.torrent.model.FileMetadata; +import jtorrent.domain.torrent.model.FileProgress; + +@Embeddable +public class FileProgressComponent { + + @Column(nullable = false) + private final byte[] verifiedPieces; + + protected FileProgressComponent() { + this(new byte[0]); + } + + public FileProgressComponent(byte[] verifiedPieces) { + this.verifiedPieces = verifiedPieces; + } + + public static FileProgressComponent fromDomain(FileProgress fileProgress) { + byte[] verifiedPieces = fileProgress.getVerifiedPieces().toByteArray(); + return new FileProgressComponent(verifiedPieces); + } + + public FileProgress toDomain(FileInfo fileInfo, FileMetadata fileMetadata) { + BitSet domainVerifiedPieces = BitSet.valueOf(verifiedPieces); + return FileProgress.createExisting(fileInfo, fileMetadata, domainVerifiedPieces); + } + + public byte[] getVerifiedPieces() { + return verifiedPieces; + } + + @Override + public String toString() { + return "FileProgressComponent{" + + ", verifiedPieces=" + Arrays.toString(verifiedPieces) + + '}'; + } +} diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentEntity.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentEntity.java new file mode 100644 index 00000000..c0189202 --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentEntity.java @@ -0,0 +1,143 @@ +package jtorrent.data.torrent.source.db.model; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jtorrent.domain.torrent.model.Torrent; +import jtorrent.domain.torrent.model.TorrentMetadata; +import jtorrent.domain.torrent.model.TorrentProgress; +import jtorrent.domain.torrent.model.TorrentStatistics; + +@Entity +public class TorrentEntity { + + @Column(nullable = false) + private final String displayName; + @Column(nullable = false) + private final String saveDirectory; + @Embedded + private final TorrentMetadataComponent metadata; + @Embedded + private final TorrentStatisticsComponent statistics; + @Embedded + private final TorrentProgressComponent progress; + @Enumerated + @Column(nullable = false) + private final Torrent.State state; + @Id + @Column(length = 20) + private byte[] infoHash; + + protected TorrentEntity() { + this(new byte[0], "", "", new TorrentMetadataComponent(), new TorrentStatisticsComponent(), + new TorrentProgressComponent(), Torrent.State.STOPPED); + } + + public TorrentEntity(byte[] infoHash, String displayName, String saveDirectory, TorrentMetadataComponent metadata, + TorrentStatisticsComponent statistics, TorrentProgressComponent progress, Torrent.State state) { + this.infoHash = infoHash; + this.displayName = displayName; + this.saveDirectory = saveDirectory; + this.metadata = metadata; + this.statistics = statistics; + this.progress = progress; + this.state = state; + } + + public static TorrentEntity fromDomain(Torrent torrent) { + return new TorrentEntity( + torrent.getInfoHash().getBytes(), + torrent.getName(), + torrent.getSaveDirectory().toString(), + TorrentMetadataComponent.fromDomain(torrent.getMetadata()), + TorrentStatisticsComponent.fromDomain(torrent.getStatistics()), + TorrentProgressComponent.fromDomain(torrent.getProgress()), + torrent.getState() + ); + } + + public Torrent toDomain() { + TorrentMetadata domainMetadata = metadata.toDomain(); + TorrentStatistics domainStatistics = statistics.toDomain(); + TorrentProgress domainProgress = progress.toDomain(domainMetadata.fileInfo()); + Path domainSaveDirectory = Paths.get(saveDirectory); + return new Torrent(domainMetadata, domainStatistics, domainProgress, displayName, domainSaveDirectory, state); + } + + public byte[] getInfoHash() { + return infoHash; + } + + public String getDisplayName() { + return displayName; + } + + public String getSaveDirectory() { + return saveDirectory; + } + + public TorrentMetadataComponent getMetadata() { + return metadata; + } + + public TorrentStatisticsComponent getStatistics() { + return statistics; + } + + public TorrentProgressComponent getProgress() { + return progress; + } + + public Torrent.State getState() { + return state; + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(infoHash); + result = 31 * result + displayName.hashCode(); + result = 31 * result + saveDirectory.hashCode(); + result = 31 * result + metadata.hashCode(); + result = 31 * result + statistics.hashCode(); + result = 31 * result + progress.hashCode(); + result = 31 * result + state.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TorrentEntity that = (TorrentEntity) o; + return Arrays.equals(infoHash, that.infoHash) + && displayName.equals(that.displayName) + && saveDirectory.equals(that.saveDirectory) + && metadata.equals(that.metadata) + && statistics.equals(that.statistics) + && progress.equals(that.progress) + && state == that.state; + } + + @Override + public String toString() { + return "TorrentEntity{" + + "infoHash=" + Arrays.toString(infoHash) + + ", displayName='" + displayName + '\'' + + ", saveDirectory='" + saveDirectory + '\'' + + ", metadata=" + metadata + + ", statistics=" + statistics + + ", progress=" + progress + + '}'; + } +} diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java new file mode 100644 index 00000000..3afd4e2f --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java @@ -0,0 +1,96 @@ +package jtorrent.data.torrent.source.db.model; + +import java.net.URI; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jtorrent.domain.torrent.model.FileInfo; +import jtorrent.domain.torrent.model.TorrentMetadata; + +@Embeddable +public class TorrentMetadataComponent { + + @ElementCollection + private final Set trackers; + + @Column(nullable = false) + private final LocalDateTime creationDate; + + @Column(nullable = false) + private final String comment; + + @Column(nullable = false) + private final String createdBy; + + @Embedded + private final FileInfoComponent fileInfo; + + protected TorrentMetadataComponent() { + this(Collections.emptySet(), LocalDateTime.MIN, "", "", new FileInfoComponent()); + } + + public TorrentMetadataComponent(Set trackers, LocalDateTime creationDate, String comment, String createdBy, + FileInfoComponent fileInfo) { + this.trackers = trackers; + this.creationDate = creationDate; + this.comment = comment; + this.createdBy = createdBy; + this.fileInfo = fileInfo; + } + + public static TorrentMetadataComponent fromDomain(TorrentMetadata torrentMetadata) { + Set trackers = torrentMetadata.trackers().stream() + .map(URI::toString) + .collect(Collectors.toSet()); + LocalDateTime creationDate = torrentMetadata.creationDate(); + String comment = torrentMetadata.comment(); + String createdBy = torrentMetadata.createdBy(); + FileInfoComponent fileInfo = FileInfoComponent.fromDomain(torrentMetadata.fileInfo()); + return new TorrentMetadataComponent(trackers, creationDate, comment, createdBy, fileInfo); + } + + public TorrentMetadata toDomain() { + Set domainTrackers = trackers.stream() + .map(URI::create) + .collect(Collectors.toSet()); + FileInfo domainFileInfo = fileInfo.toDomain(); + return new TorrentMetadata(domainTrackers, creationDate, comment, createdBy, domainFileInfo); + } + + public Set getTrackers() { + return trackers; + } + + public LocalDateTime getCreationDate() { + return creationDate; + } + + public String getComment() { + return comment; + } + + public String getCreatedBy() { + return createdBy; + } + + public FileInfoComponent getFileInfo() { + return fileInfo; + } + + @Override + public String toString() { + return "TorrentMetadataComponent{" + + "trackers=" + trackers + + ", creationDate=" + creationDate + + ", comment='" + comment + '\'' + + ", createdBy='" + createdBy + '\'' + + ", fileInfo=" + fileInfo + + '}'; + } +} diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java new file mode 100644 index 00000000..83747d6b --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java @@ -0,0 +1,85 @@ +package jtorrent.data.torrent.source.db.model; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embeddable; +import jtorrent.domain.torrent.model.FileInfo; +import jtorrent.domain.torrent.model.FileProgress; +import jtorrent.domain.torrent.model.TorrentProgress; + +@Embeddable +public class TorrentProgressComponent { + + @ElementCollection + @CollectionTable + private final Map pathToFileProgress; + + @ElementCollection + @CollectionTable + private final Map pieceToReceivedBlocks; + + @Column(nullable = false) + private final byte[] verifiedPieces; + + protected TorrentProgressComponent() { + this(Collections.emptyMap(), Collections.emptyMap(), new byte[0]); + } + + public TorrentProgressComponent(Map pathToFileProgress, + Map pieceToReceivedBlocks, byte[] verifiedPieces) { + this.pathToFileProgress = pathToFileProgress; + this.pieceToReceivedBlocks = pieceToReceivedBlocks; + this.verifiedPieces = verifiedPieces; + } + + public static TorrentProgressComponent fromDomain(TorrentProgress torrentProgress) { + byte[] verifiedPieces = torrentProgress.getVerifiedPieces().toByteArray(); + Map pathToFileProgress = torrentProgress.getFileProgress().entrySet().stream() + .map(e -> Map.entry(e.getKey().toString(), FileProgressComponent.fromDomain(e.getValue()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + Map pieceToReceivedBlocks = torrentProgress.getReceivedBlocks(); + return new TorrentProgressComponent(pathToFileProgress, pieceToReceivedBlocks, verifiedPieces); + } + + public TorrentProgress toDomain(FileInfo fileInfo) { + Map domainFileProgress = pathToFileProgress.entrySet().stream() + .map(e -> { + Path path = Path.of(e.getKey()); + FileProgress fileProgress = e.getValue().toDomain(fileInfo, fileInfo.getFileMetaData(path)); + return Map.entry(path, fileProgress); + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + BitSet domainVerifiedPieces = BitSet.valueOf(verifiedPieces); + return TorrentProgress.createExisting(fileInfo, domainFileProgress, domainVerifiedPieces, + pieceToReceivedBlocks); + } + + public Map getPathToFileProgress() { + return pathToFileProgress; + } + + public Map getPieceToReceivedBlocks() { + return pieceToReceivedBlocks; + } + + public byte[] getVerifiedPieces() { + return verifiedPieces; + } + + @Override + public String toString() { + return "TorrentProgressComponent{" + + "pathToFileProgress=" + pathToFileProgress + + ", pieceToReceivedBlocks=" + pieceToReceivedBlocks + + ", verifiedPieces=" + Arrays.toString(verifiedPieces) + + '}'; + } +} diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentStatisticsComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentStatisticsComponent.java new file mode 100644 index 00000000..15b3ce19 --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentStatisticsComponent.java @@ -0,0 +1,46 @@ +package jtorrent.data.torrent.source.db.model; + +import jakarta.persistence.Column; +import jtorrent.domain.torrent.model.TorrentStatistics; + +public class TorrentStatisticsComponent { + + @Column(nullable = false) + private final long downloaded; + + @Column(nullable = false) + private final long uploaded; + + protected TorrentStatisticsComponent() { + this(0, 0); + } + + public TorrentStatisticsComponent(long downloaded, long uploaded) { + this.downloaded = downloaded; + this.uploaded = uploaded; + } + + public static TorrentStatisticsComponent fromDomain(TorrentStatistics torrentStatistics) { + return new TorrentStatisticsComponent(torrentStatistics.getDownloaded(), torrentStatistics.getUploaded()); + } + + public TorrentStatistics toDomain() { + return new TorrentStatistics(downloaded, uploaded); + } + + public long getDownloaded() { + return downloaded; + } + + public long getUploaded() { + return uploaded; + } + + @Override + public String toString() { + return "TorrentStatisticsComponent{" + + "downloaded=" + downloaded + + ", uploaded=" + uploaded + + '}'; + } +} diff --git a/src/main/java/jtorrent/data/torrent/source/db/util/HibernateUtil.java b/src/main/java/jtorrent/data/torrent/source/db/util/HibernateUtil.java new file mode 100644 index 00000000..9077add8 --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/db/util/HibernateUtil.java @@ -0,0 +1,27 @@ +package jtorrent.data.torrent.source.db.util; + +import org.hibernate.SessionFactory; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; + +import jtorrent.data.torrent.source.db.model.TorrentEntity; + +public class HibernateUtil { + + private static SessionFactory sessionFactory; + + private HibernateUtil() { + } + + public static synchronized SessionFactory getSessionFactory() { + if (sessionFactory == null) { + StandardServiceRegistry registry = new StandardServiceRegistryBuilder().build(); + sessionFactory = new MetadataSources(registry) + .addAnnotatedClass(TorrentEntity.class) + .buildMetadata() + .buildSessionFactory(); + } + return sessionFactory; + } +} diff --git a/src/main/java/jtorrent/domain/Client.java b/src/main/java/jtorrent/domain/Client.java index e0b467db..bc135d6b 100644 --- a/src/main/java/jtorrent/domain/Client.java +++ b/src/main/java/jtorrent/domain/Client.java @@ -88,7 +88,7 @@ public void shutdown() { } public void addTorrent(TorrentMetadata torrentMetaData, String name, Path saveDirectory) { - Torrent torrent = new Torrent(torrentMetaData, name, saveDirectory); + Torrent torrent = Torrent.createNew(torrentMetaData, name, saveDirectory); torrentRepository.addTorrent(torrent); } diff --git a/src/main/java/jtorrent/domain/torrent/model/FileInfo.java b/src/main/java/jtorrent/domain/torrent/model/FileInfo.java index 304b0962..e5f7bba0 100644 --- a/src/main/java/jtorrent/domain/torrent/model/FileInfo.java +++ b/src/main/java/jtorrent/domain/torrent/model/FileInfo.java @@ -18,7 +18,7 @@ public abstract class FileInfo { protected final List fileMetaData; protected final List pieceHashes; protected final int pieceSize; - private final Sha1Hash infoHash; + protected final Sha1Hash infoHash; protected FileInfo(List fileMetaData, int pieceSize, List pieceHashes, Sha1Hash infoHash) { this.fileMetaData = requireNonNull(fileMetaData); @@ -117,6 +117,13 @@ public List getFileMetaData() { return fileMetaData; } + public FileMetadata getFileMetaData(Path path) { + return fileMetaData.stream() + .filter(file -> file.path().equals(path)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("File not found: " + path)); + } + public List getPieceHashes() { return pieceHashes; } diff --git a/src/main/java/jtorrent/domain/torrent/model/FileProgress.java b/src/main/java/jtorrent/domain/torrent/model/FileProgress.java index ffddb055..5c269343 100644 --- a/src/main/java/jtorrent/domain/torrent/model/FileProgress.java +++ b/src/main/java/jtorrent/domain/torrent/model/FileProgress.java @@ -1,7 +1,10 @@ package jtorrent.domain.torrent.model; +import static jtorrent.domain.common.util.ValidationUtil.requireNonNull; + import java.util.BitSet; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.subjects.BehaviorSubject; @@ -10,18 +13,51 @@ public class FileProgress { private final FileInfo fileInfo; private final FileMetadata fileMetaData; - private final AtomicLong verifiedBytes = new AtomicLong(0L); - private final BehaviorSubject verifiedBytesSubject = BehaviorSubject.createDefault(0L); - private final BitSet verifiedPieces = new BitSet(); - private final BehaviorSubject verifiedPiecesSubject = BehaviorSubject.createDefault(new BitSet()); + private final AtomicLong verifiedBytes; + private final BehaviorSubject verifiedBytesSubject; + private final BitSet verifiedPieces; + private final BehaviorSubject verifiedPiecesSubject; - public FileProgress(FileInfo fileInfo, FileMetadata fileMetaData) { - this.fileInfo = fileInfo; - this.fileMetaData = fileMetaData; + public FileProgress(FileInfo fileInfo, FileMetadata fileMetaData, long verifiedBytes, BitSet verifiedPieces) { + this.fileInfo = requireNonNull(fileInfo); + this.fileMetaData = requireNonNull(fileMetaData); + this.verifiedBytes = new AtomicLong(verifiedBytes); + this.verifiedBytesSubject = BehaviorSubject.createDefault(verifiedBytes); + this.verifiedPieces = requireNonNull(verifiedPieces); + this.verifiedPiecesSubject = BehaviorSubject.createDefault((BitSet) verifiedPieces.clone()); } - private void incrementVerifiedBytes(long bytes) { - verifiedBytesSubject.onNext(verifiedBytes.addAndGet(bytes)); + public static FileProgress createNew(FileInfo fileInfo, FileMetadata fileMetaData) { + return new FileProgress(fileInfo, fileMetaData, 0, new BitSet()); + } + + public static FileProgress createExisting(FileInfo fileInfo, FileMetadata fileMetaData, + BitSet verifiedPieces) { + long verifiedBytes = IntStream.range(fileMetaData.firstPiece(), fileMetaData.lastPiece() + 1) + .filter(verifiedPieces::get) + .mapToLong(piece -> getPieceBytesInFile(fileInfo, fileMetaData, piece)) + .sum(); + return new FileProgress(fileInfo, fileMetaData, verifiedBytes, verifiedPieces); + } + + private static long getPieceBytesInFile(FileInfo fileInfo, FileMetadata fileMetaData, int piece) { + long pieceStart = fileInfo.getPieceOffset(piece); + long pieceEnd = pieceStart + fileInfo.getPieceSize(piece) - 1; + long pieceStartWithinFile = Math.max(pieceStart, fileMetaData.start()); + long pieceEndWithinFile = Math.min(pieceEnd, fileMetaData.end()); + return pieceEndWithinFile - pieceStartWithinFile + 1; + } + + private long getPieceBytesInFile(int piece) { + return getPieceBytesInFile(fileInfo, fileMetaData, piece); + } + + public long getVerifiedBytes() { + return verifiedBytes.get(); + } + + public BitSet getVerifiedPieces() { + return verifiedPieces; } public Observable getVerifiedBytesObservable() { @@ -42,6 +78,14 @@ public void setPieceVerified(int piece) { incrementVerifiedBytes(getPieceBytesInFile(piece)); } + private void incrementVerifiedBytes(long bytes) { + verifiedBytesSubject.onNext(verifiedBytes.addAndGet(bytes)); + } + + private int getRelativePieceIndex(int piece) { + return piece - fileMetaData.firstPiece(); + } + public void setPieceNotVerified(int piece) { int relativePiece = getRelativePieceIndex(piece); if (!verifiedPieces.get(relativePiece)) { @@ -52,15 +96,38 @@ public void setPieceNotVerified(int piece) { incrementVerifiedBytes(-getPieceBytesInFile(piece)); } - private long getPieceBytesInFile(int piece) { - long pieceStart = fileInfo.getPieceOffset(piece); - long pieceEnd = pieceStart + fileInfo.getPieceSize(piece) - 1; - long pieceStartWithinFile = Math.max(pieceStart, fileMetaData.start()); - long pieceEndWithinFile = Math.min(pieceEnd, fileMetaData.end()); - return pieceEndWithinFile - pieceStartWithinFile + 1; + @Override + public int hashCode() { + int result = fileInfo.hashCode(); + result = 31 * result + fileMetaData.hashCode(); + result = 31 * result + verifiedBytes.hashCode(); + result = 31 * result + verifiedPieces.hashCode(); + return result; } - private int getRelativePieceIndex(int piece) { - return piece - fileMetaData.firstPiece(); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FileProgress that = (FileProgress) o; + return fileInfo.equals(that.fileInfo) + && fileMetaData.equals(that.fileMetaData) + && (verifiedBytes.get() == that.verifiedBytes.get()) + && verifiedPieces.equals(that.verifiedPieces); + } + + @Override + public String toString() { + return "FileProgress{" + + "fileInfo=" + fileInfo + + ", fileMetaData=" + fileMetaData + + ", verifiedBytes=" + verifiedBytes + + ", verifiedPieces=" + verifiedPieces + + '}'; } } diff --git a/src/main/java/jtorrent/domain/torrent/model/MultiFileInfo.java b/src/main/java/jtorrent/domain/torrent/model/MultiFileInfo.java index deef7626..36c1c868 100644 --- a/src/main/java/jtorrent/domain/torrent/model/MultiFileInfo.java +++ b/src/main/java/jtorrent/domain/torrent/model/MultiFileInfo.java @@ -25,6 +25,10 @@ public Path getFileRoot() { @Override public String getName() { + return getDirectory(); + } + + public String getDirectory() { return directory; } @@ -52,9 +56,10 @@ public boolean equals(Object o) { public String toString() { return "MultiFileInfo{" + "directory='" + directory + '\'' - + ", fileWithPieceInfos=" + fileMetaData + + ", fileMetaData=" + fileMetaData + ", pieceHashes=" + pieceHashes + ", pieceSize=" + pieceSize + + ", infoHash=" + infoHash + '}'; } } diff --git a/src/main/java/jtorrent/domain/torrent/model/SingleFileInfo.java b/src/main/java/jtorrent/domain/torrent/model/SingleFileInfo.java index c7af2eea..a4a8a406 100644 --- a/src/main/java/jtorrent/domain/torrent/model/SingleFileInfo.java +++ b/src/main/java/jtorrent/domain/torrent/model/SingleFileInfo.java @@ -24,9 +24,10 @@ public String getName() { @Override public String toString() { return "SingleFileInfo{" - + "fileWithPieceInfos=" + fileMetaData + + "fileMetaData=" + fileMetaData + ", pieceHashes=" + pieceHashes + ", pieceSize=" + pieceSize - + "} "; + + ", infoHash=" + infoHash + + '}'; } } diff --git a/src/main/java/jtorrent/domain/torrent/model/Torrent.java b/src/main/java/jtorrent/domain/torrent/model/Torrent.java index 3b95fb6a..a3e07155 100644 --- a/src/main/java/jtorrent/domain/torrent/model/Torrent.java +++ b/src/main/java/jtorrent/domain/torrent/model/Torrent.java @@ -3,11 +3,11 @@ import static jtorrent.domain.common.util.ValidationUtil.requireNonNull; import java.nio.file.Path; -import java.nio.file.Paths; import java.time.LocalDateTime; import java.util.BitSet; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -26,7 +26,7 @@ public class Torrent implements TrackerHandler.TorrentProgressProvider { private final TorrentMetadata torrentMetaData; - private final TorrentStatistics torrentStatistics = new TorrentStatistics(); + private final TorrentStatistics torrentStatistics; private final TorrentProgress torrentProgress; private final Set trackers = new HashSet<>(); private final MutableRxObservableSet peers = new MutableRxObservableSet<>(new HashSet<>()); @@ -36,24 +36,30 @@ public class Torrent implements TrackerHandler.TorrentProgressProvider { private String name; private Path saveDirectory; - private State state = State.STOPPED; - private final BehaviorSubject stateSubject = BehaviorSubject.createDefault(state); + private State state; + private final BehaviorSubject stateSubject; - public Torrent(TorrentMetadata torrentMetaData) { - // TODO: use default downloads folder? - this(torrentMetaData, torrentMetaData.fileInfo().getName(), Paths.get("download").toAbsolutePath()); - } - - public Torrent(TorrentMetadata torrentMetaData, String name, Path saveDirectory) { + public Torrent(TorrentMetadata torrentMetaData, TorrentStatistics torrentStatistics, + TorrentProgress torrentProgress, String name, Path saveDirectory, State state) { this.torrentMetaData = requireNonNull(torrentMetaData); - this.torrentProgress = new TorrentProgress(torrentMetaData.fileInfo()); + this.torrentStatistics = requireNonNull(torrentStatistics); + this.torrentProgress = requireNonNull(torrentProgress); this.name = name; - this.saveDirectory = saveDirectory; + this.saveDirectory = requireNonNull(saveDirectory); + this.state = requireNonNull(state); + this.stateSubject = BehaviorSubject.createDefault(state); + torrentMetaData.trackers().stream() .map(TrackerFactory::fromUri) .collect(Collectors.toCollection(() -> trackers)); } + public static Torrent createNew(TorrentMetadata torrentMetaData, String name, Path saveDirectory) { + TorrentStatistics torrentStatistics = TorrentStatistics.createNew(); + TorrentProgress torrentProgress = TorrentProgress.createNew(torrentMetaData.fileInfo()); + return new Torrent(torrentMetaData, torrentStatistics, torrentProgress, name, saveDirectory, State.STOPPED); + } + public Path getSaveDirectory() { return saveDirectory; } @@ -135,7 +141,7 @@ public List getFileMetaDataWithState() { return torrentMetaData.fileInfo().getFileMetaData() .stream() .map(fileMetaData -> new FileMetadataWithState(fileMetaData, - torrentProgress.getFileState(fileMetaData.path()))) + torrentProgress.getFileProgress(fileMetaData.path()))) .toList(); } @@ -318,11 +324,62 @@ public Observable getStateObservable() { return stateSubject; } + public TorrentMetadata getMetadata() { + return torrentMetaData; + } + + public TorrentStatistics getStatistics() { + return torrentStatistics; + } + + public TorrentProgress getProgress() { + return torrentProgress; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Torrent torrent = (Torrent) o; + return torrentMetaData.equals(torrent.torrentMetaData) + && torrentStatistics.equals(torrent.torrentStatistics) + && torrentProgress.equals(torrent.torrentProgress) + && trackers.equals(torrent.trackers) + && peers.equals(torrent.peers) + && Objects.equals(name, torrent.name) + && saveDirectory.equals(torrent.saveDirectory) + && state == torrent.state; + } + + @Override + public int hashCode() { + int result = torrentMetaData.hashCode(); + result = 31 * result + torrentStatistics.hashCode(); + result = 31 * result + torrentProgress.hashCode(); + result = 31 * result + trackers.hashCode(); + result = 31 * result + peers.hashCode(); + result = 31 * result + Objects.hashCode(name); + result = 31 * result + saveDirectory.hashCode(); + result = 31 * result + state.hashCode(); + return result; + } + @Override public String toString() { return "Torrent{" - + "trackers=" + trackers + + "torrentMetaData=" + torrentMetaData + + ", torrentStatistics=" + torrentStatistics + + ", torrentProgress=" + torrentProgress + + ", trackers=" + trackers + + ", peers=" + peers + ", name='" + name + '\'' + + ", saveDirectory=" + saveDirectory + + ", state=" + state + '}'; } @@ -332,5 +389,4 @@ public enum State { DOWNLOADING, SEEDING } - } diff --git a/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java b/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java index 211c175c..616a3cce 100644 --- a/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java +++ b/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.IntStream; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.subjects.BehaviorSubject; @@ -16,41 +15,99 @@ public class TorrentProgress { private final FileInfo fileInfo; - private final Map pathToFileState = new HashMap<>(); - - private final AtomicLong verifiedBytes = new AtomicLong(0); - private final BehaviorSubject verifiedBytesSubject = BehaviorSubject.createDefault(0L); - private final BehaviorSubject checkedBytesSubject = BehaviorSubject.createDefault(0L); - private final BitSet completePieces = new BitSet(); - private final BitSet verifiedPieces = new BitSet(); - private final BehaviorSubject verifiedPiecesSubject = BehaviorSubject.createDefault(new BitSet()); + private final Map pathToFileProgress; + + private final AtomicLong verifiedBytes; + private final BehaviorSubject verifiedBytesSubject; + private final BehaviorSubject checkedBytesSubject; + private final BitSet completePieces; + private final BitSet verifiedPieces; + private final BehaviorSubject verifiedPiecesSubject; private final BehaviorSubject availablePiecesSubject = BehaviorSubject.createDefault(new BitSet()); private final Map pieceIndexToRequestedBlocks = new HashMap<>(); - private final Map pieceIndexToAvailableBlocks = new HashMap<>(); - private final BitSet partiallyMissingPieces = new BitSet(); - private final BitSet partiallyMissingPiecesWithUnrequestedBlocks = new BitSet(); - private final BitSet completelyMissingPieces = new BitSet(); - private final BitSet completelyMissingPiecesWithUnrequestedBlocks = new BitSet(); - private long checkedBytes = 0; - - public TorrentProgress(FileInfo fileInfo) { + private final Map pieceIndexToAvailableBlocks; + private final BitSet partiallyMissingPieces; + private final BitSet partiallyMissingPiecesWithUnrequestedBlocks; + private final BitSet completelyMissingPieces; + private final BitSet completelyMissingPiecesWithUnrequestedBlocks; + private long checkedBytes; + + public TorrentProgress(FileInfo fileInfo, Map pathToFileProgress, long verifiedBytes, + BitSet completePieces, BitSet verifiedPieces, Map pieceIndexToAvailableBlocks, + BitSet partiallyMissingPieces, BitSet partiallyMissingPiecesWithUnrequestedBlocks, + BitSet completelyMissingPieces, BitSet completelyMissingPiecesWithUnrequestedBlocks, long checkedBytes) { this.fileInfo = requireNonNull(fileInfo); + this.pathToFileProgress = pathToFileProgress; + this.verifiedBytes = new AtomicLong(verifiedBytes); + this.verifiedBytesSubject = BehaviorSubject.createDefault(verifiedBytes); + this.checkedBytesSubject = BehaviorSubject.createDefault(checkedBytes); + this.completePieces = completePieces; + this.verifiedPieces = verifiedPieces; + this.verifiedPiecesSubject = BehaviorSubject.createDefault(verifiedPieces); + this.pieceIndexToAvailableBlocks = pieceIndexToAvailableBlocks; + this.partiallyMissingPieces = partiallyMissingPieces; + this.partiallyMissingPiecesWithUnrequestedBlocks = partiallyMissingPiecesWithUnrequestedBlocks; + this.completelyMissingPieces = completelyMissingPieces; + this.completelyMissingPiecesWithUnrequestedBlocks = completelyMissingPiecesWithUnrequestedBlocks; + this.checkedBytes = checkedBytes; + } + + public static TorrentProgress createNew(FileInfo fileInfo) { + Map pathToFileProgress = new HashMap<>(); + fileInfo.getFileMetaData() + .forEach(fileMetaData -> pathToFileProgress.put(fileMetaData.path(), + FileProgress.createNew(fileInfo, fileMetaData))); + BitSet completePieces = new BitSet(); + BitSet verifiedPieces = new BitSet(); + Map pieceIndexToAvailableBlocks = new HashMap<>(); + BitSet partiallyMissingPieces = new BitSet(); + BitSet partiallyMissingPiecesWithUnrequestedBlocks = new BitSet(); + BitSet completelyMissingPieces = new BitSet(); + completelyMissingPieces.set(0, fileInfo.getNumPieces()); + BitSet completelyMissingPiecesWithUnrequestedBlocks = new BitSet(); + completelyMissingPiecesWithUnrequestedBlocks.set(0, fileInfo.getNumPieces()); + return new TorrentProgress(fileInfo, pathToFileProgress, 0, completePieces, verifiedPieces, + pieceIndexToAvailableBlocks, partiallyMissingPieces, + partiallyMissingPiecesWithUnrequestedBlocks, completelyMissingPieces, + completelyMissingPiecesWithUnrequestedBlocks, 0); + } - IntStream.range(0, fileInfo.getNumPieces()) - .forEach(i -> { - pieceIndexToRequestedBlocks.put(i, new BitSet()); - pieceIndexToAvailableBlocks.put(i, new BitSet()); - completelyMissingPieces.set(i); - completelyMissingPiecesWithUnrequestedBlocks.set(i); - }); + public static TorrentProgress createExisting(FileInfo fileInfo, Map pathToFileProgress, + BitSet verifiedPieces, Map pieceIndexToAvailableBlocks) { - fileInfo.getFileMetaData() - .forEach(fileMetaData -> pathToFileState.put(fileMetaData.path(), - new FileProgress(fileInfo, fileMetaData))); + long verifiedBytes = verifiedPieces.stream() + .mapToLong(fileInfo::getPieceSize) + .sum(); + + BitSet completePieces = new BitSet(); + BitSet partiallyMissingPieces = new BitSet(); + BitSet completelyMissingPieces = new BitSet(); + + pieceIndexToAvailableBlocks.forEach((piece, availableBlocks) -> { + if (availableBlocks.cardinality() == fileInfo.getNumBlocks(piece)) { + completePieces.set(piece); + } else if (availableBlocks.cardinality() > 0) { + partiallyMissingPieces.set(piece); + } else { + completelyMissingPieces.set(piece); + } + }); + + BitSet partiallyMissingPiecesWithUnrequestedBlocks = (BitSet) partiallyMissingPieces.clone(); + BitSet completelyMissingPiecesWithUnrequestedBlocks = (BitSet) completelyMissingPieces.clone(); + + return new TorrentProgress(fileInfo, pathToFileProgress, verifiedBytes, completePieces, verifiedPieces, + pieceIndexToAvailableBlocks, partiallyMissingPieces, + partiallyMissingPiecesWithUnrequestedBlocks, completelyMissingPieces, + completelyMissingPiecesWithUnrequestedBlocks, 0); + } + + public Map getFileProgress() { + return pathToFileProgress; } - public FileProgress getFileState(Path path) { - return pathToFileState.get(path); + public FileProgress getFileProgress(Path path) { + return pathToFileProgress.get(path); } public Observable getVerifiedBytesObservable() { @@ -95,7 +152,7 @@ public synchronized void setPieceVerified(int piece) { filesInRange.stream() .map(FileMetadata::path) - .map(pathToFileState::get) + .map(pathToFileProgress::get) .forEach(fileProgress -> fileProgress.setPieceVerified(piece)); } @@ -117,14 +174,14 @@ public synchronized void setPieceMissing(int piece) { filesInRange.stream() .map(FileMetadata::path) - .map(pathToFileState::get) + .map(pathToFileProgress::get) .forEach(fileProgress -> fileProgress.setPieceNotVerified(piece)); } verifiedPieces.clear(piece); verifiedPiecesSubject.onNext((BitSet) verifiedPieces.clone()); completePieces.clear(piece); - pieceIndexToAvailableBlocks.get(piece).clear(); + getAvailableBlocks(piece).clear(); partiallyMissingPieces.clear(piece); partiallyMissingPiecesWithUnrequestedBlocks.clear(piece); completelyMissingPieces.set(piece); @@ -155,8 +212,12 @@ private BitSet getUnavailableBlocks(int piece) { return unavailableBlocks; } + public Map getReceivedBlocks() { + return pieceIndexToAvailableBlocks; + } + private BitSet getAvailableBlocks(int piece) { - return pieceIndexToAvailableBlocks.get(piece); + return pieceIndexToAvailableBlocks.computeIfAbsent(piece, k -> new BitSet()); } private BitSet getUnrequestedBlocks(int piece) { @@ -167,11 +228,11 @@ private BitSet getUnrequestedBlocks(int piece) { } private BitSet getRequestedBlocks(int piece) { - return pieceIndexToRequestedBlocks.get(piece); + return pieceIndexToRequestedBlocks.computeIfAbsent(piece, k -> new BitSet()); } public synchronized void setBlockRequested(int pieceIndex, int blockIndex) { - BitSet requestedBlocks = pieceIndexToRequestedBlocks.get(pieceIndex); + BitSet requestedBlocks = getRequestedBlocks(pieceIndex); requestedBlocks.set(blockIndex); if (hasUnavailableAndUnrequestedBlocks(pieceIndex)) { @@ -194,7 +255,7 @@ private boolean isPiecePartiallyMissing(int piece) { } public synchronized void setBlockNotRequested(int pieceIndex, int blockIndex) { - BitSet requestedBlocks = pieceIndexToRequestedBlocks.get(pieceIndex); + BitSet requestedBlocks = getRequestedBlocks(pieceIndex); requestedBlocks.clear(blockIndex); if (isPieceCompletelyMissing(pieceIndex) && hasUnavailableAndUnrequestedBlocks(pieceIndex)) { @@ -219,7 +280,7 @@ public synchronized void setBlockReceived(int pieceIndex, int blockIndex) { } private boolean isBlockAvailable(int piece, int blockIndex) { - return pieceIndexToAvailableBlocks.get(piece).get(blockIndex); + return getAvailableBlocks(piece).get(blockIndex); } private void setPieceComplete(int piece) { @@ -294,10 +355,70 @@ public synchronized BitSet getMissingBlocks(int piece) { } private boolean hasUnrequestedBlocks(int piece) { - return pieceIndexToRequestedBlocks.get(piece).cardinality() < fileInfo.getNumBlocks(piece); + return getRequestedBlocks(piece).cardinality() < fileInfo.getNumBlocks(piece); } private boolean hasUnavailableBlocks(int piece) { - return pieceIndexToAvailableBlocks.get(piece).cardinality() < fileInfo.getNumBlocks(piece); + return getAvailableBlocks(piece).cardinality() < fileInfo.getNumBlocks(piece); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TorrentProgress that = (TorrentProgress) o; + return checkedBytes == that.checkedBytes + && fileInfo.equals(that.fileInfo) + && pathToFileProgress.equals(that.pathToFileProgress) + && (verifiedBytes.get() == that.verifiedBytes.get()) + && completePieces.equals(that.completePieces) + && verifiedPieces.equals(that.verifiedPieces) + && pieceIndexToRequestedBlocks.equals(that.pieceIndexToRequestedBlocks) + && pieceIndexToAvailableBlocks.equals(that.pieceIndexToAvailableBlocks) + && partiallyMissingPieces.equals(that.partiallyMissingPieces) + && partiallyMissingPiecesWithUnrequestedBlocks.equals(that.partiallyMissingPiecesWithUnrequestedBlocks) + && completelyMissingPieces.equals(that.completelyMissingPieces) + && completelyMissingPiecesWithUnrequestedBlocks.equals( + that.completelyMissingPiecesWithUnrequestedBlocks); + } + + @Override + public int hashCode() { + int result = fileInfo.hashCode(); + result = 31 * result + pathToFileProgress.hashCode(); + result = 31 * result + verifiedBytes.hashCode(); + result = 31 * result + completePieces.hashCode(); + result = 31 * result + verifiedPieces.hashCode(); + result = 31 * result + pieceIndexToRequestedBlocks.hashCode(); + result = 31 * result + pieceIndexToAvailableBlocks.hashCode(); + result = 31 * result + partiallyMissingPieces.hashCode(); + result = 31 * result + partiallyMissingPiecesWithUnrequestedBlocks.hashCode(); + result = 31 * result + completelyMissingPieces.hashCode(); + result = 31 * result + completelyMissingPiecesWithUnrequestedBlocks.hashCode(); + result = 31 * result + Long.hashCode(checkedBytes); + return result; + } + + @Override + public String toString() { + return "TorrentProgress{" + + "fileInfo=" + fileInfo + + ", pathToFileProgress=" + pathToFileProgress + + ", verifiedBytes=" + verifiedBytes + + ", completePieces=" + completePieces + + ", verifiedPieces=" + verifiedPieces + + ", pieceIndexToRequestedBlocks=" + pieceIndexToRequestedBlocks + + ", pieceIndexToAvailableBlocks=" + pieceIndexToAvailableBlocks + + ", partiallyMissingPieces=" + partiallyMissingPieces + + ", partiallyMissingPiecesWithUnrequestedBlocks=" + partiallyMissingPiecesWithUnrequestedBlocks + + ", completelyMissingPieces=" + completelyMissingPieces + + ", completelyMissingPiecesWithUnrequestedBlocks=" + completelyMissingPiecesWithUnrequestedBlocks + + ", checkedBytes=" + checkedBytes + + '}'; } } diff --git a/src/main/java/jtorrent/domain/torrent/model/TorrentStatistics.java b/src/main/java/jtorrent/domain/torrent/model/TorrentStatistics.java index 55a0aa54..964453ae 100644 --- a/src/main/java/jtorrent/domain/torrent/model/TorrentStatistics.java +++ b/src/main/java/jtorrent/domain/torrent/model/TorrentStatistics.java @@ -1,5 +1,7 @@ package jtorrent.domain.torrent.model; +import static jtorrent.domain.common.util.ValidationUtil.requireNonNegative; + import java.util.concurrent.atomic.AtomicLong; import io.reactivex.rxjava3.core.Observable; @@ -7,11 +9,26 @@ public class TorrentStatistics { - private final AtomicLong downloaded = new AtomicLong(0); - private final BehaviorSubject downloadedSubject = BehaviorSubject.createDefault(0L); + private final AtomicLong downloaded; + private final BehaviorSubject downloadedSubject; + + private final AtomicLong uploaded; + private final BehaviorSubject uploadedSubject; + + public TorrentStatistics(long downloaded, long uploaded) { + requireNonNegative(downloaded); + requireNonNegative(uploaded); + + this.downloaded = new AtomicLong(downloaded); + this.downloadedSubject = BehaviorSubject.createDefault(downloaded); - private final AtomicLong uploaded = new AtomicLong(0); - private final BehaviorSubject uploadedSubject = BehaviorSubject.createDefault(0L); + this.uploaded = new AtomicLong(uploaded); + this.uploadedSubject = BehaviorSubject.createDefault(uploaded); + } + + public static TorrentStatistics createNew() { + return new TorrentStatistics(0, 0); + } public void incrementDownloaded(long bytes) { downloadedSubject.onNext(downloaded.addAndGet(bytes)); @@ -36,4 +53,32 @@ public long getUploaded() { public Observable getUploadedObservable() { return uploadedSubject; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TorrentStatistics that = (TorrentStatistics) o; + return (downloaded.get() == that.downloaded.get()) && (uploaded.get() == that.uploaded.get()); + } + + @Override + public int hashCode() { + int result = downloaded.hashCode(); + result = 31 * result + uploaded.hashCode(); + return result; + } + + @Override + public String toString() { + return "TorrentStatistics{" + + "uploaded=" + uploaded + + ", downloaded=" + downloaded + + '}'; + } } From eb11b6904fb7c4959ed739c49356adf307d65095 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Mon, 13 May 2024 01:04:57 +0800 Subject: [PATCH 08/33] Persist torrents to database --- .../repository/FileTorrentRepository.java | 68 ++++++++++++------- src/main/java/jtorrent/domain/Client.java | 1 + .../torrent/repository/TorrentRepository.java | 14 ++-- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java b/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java index 9d89b9a7..66c04dab 100644 --- a/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java +++ b/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java @@ -25,6 +25,8 @@ import jtorrent.data.torrent.model.BencodedMultiFileInfo; import jtorrent.data.torrent.model.BencodedSingleFileInfo; import jtorrent.data.torrent.model.BencodedTorrent; +import jtorrent.data.torrent.source.db.dao.TorrentDao; +import jtorrent.data.torrent.source.db.model.TorrentEntity; import jtorrent.domain.common.util.ContinuousMergedInputStream; import jtorrent.domain.common.util.Sha1Hash; import jtorrent.domain.common.util.rx.MutableRxObservableList; @@ -35,8 +37,29 @@ public class FileTorrentRepository implements TorrentRepository { - private final MutableRxObservableList torrents = new MutableRxObservableList<>(new ArrayList<>()); - private final Map infoHashToTorrent = new HashMap<>(); + private final MutableRxObservableList torrentsObservable; + private final Map infoHashToTorrent; + private final TorrentDao torrentDao = new TorrentDao(); + + public FileTorrentRepository() { + List torrents = new ArrayList<>(); + torrentDao.readAll().stream() + .map(TorrentEntity::toDomain) + .forEach(torrents::add); + infoHashToTorrent = torrents.stream() + .collect(HashMap::new, (map, torrent) -> map.put(torrent.getInfoHash(), torrent), Map::putAll); + this.torrentsObservable = new MutableRxObservableList<>(torrents); + } + + @Override + public RxObservableList getTorrents() { + return torrentsObservable; + } + + @Override + public Torrent getTorrent(Sha1Hash infoHash) { + return infoHashToTorrent.get(infoHash); + } @Override public void addTorrent(Torrent torrent) { @@ -44,8 +67,23 @@ public void addTorrent(Torrent torrent) { // TODO: maybe throw exception if torrent already exists? return; } + torrentDao.create(TorrentEntity.fromDomain(torrent)); infoHashToTorrent.put(torrent.getInfoHash(), torrent); - torrents.add(torrent); + torrentsObservable.add(torrent); + } + + @Override + public void persistTorrents() { + infoHashToTorrent.values().stream() + .map(TorrentEntity::fromDomain) + .forEach(torrentDao::update); + } + + @Override + public void removeTorrent(Torrent torrent) { + torrentDao.delete(torrent.getInfoHash().getBytes()); + infoHashToTorrent.remove(torrent.getInfoHash()); + torrentsObservable.remove(torrent); } @Override @@ -54,6 +92,10 @@ public TorrentMetadata loadTorrent(File file) throws IOException { return loadTorrent(inputStream); } + private TorrentMetadata loadTorrent(InputStream inputStream) throws IOException { + return BencodedTorrent.decode(inputStream).toDomain(); + } + @Override public TorrentMetadata loadTorrent(URL url) throws IOException { // For some reason decoding directly from the URL stream doesn't work, so we have to read it into a byte array @@ -73,10 +115,6 @@ public TorrentMetadata loadTorrent(URL url) throws IOException { } } - private TorrentMetadata loadTorrent(InputStream inputStream) throws IOException { - return BencodedTorrent.decode(inputStream).toDomain(); - } - @Override public void saveTorrent(TorrentMetadata torrentMetadata, Path savePath) throws IOException { BencodedTorrent bencodedTorrent = BencodedTorrent.fromDomain(torrentMetadata); @@ -85,22 +123,6 @@ public void saveTorrent(TorrentMetadata torrentMetadata, Path savePath) throws I } } - @Override - public void removeTorrent(Torrent torrent) { - infoHashToTorrent.remove(torrent.getInfoHash()); - torrents.remove(torrent); - } - - @Override - public RxObservableList getTorrents() { - return torrents; - } - - @Override - public Torrent getTorrent(Sha1Hash infoHash) { - return infoHashToTorrent.get(infoHash); - } - /** * Create a new {@link TorrentMetadata} instance with the current time as the creation date. * diff --git a/src/main/java/jtorrent/domain/Client.java b/src/main/java/jtorrent/domain/Client.java index bc135d6b..12b2eec7 100644 --- a/src/main/java/jtorrent/domain/Client.java +++ b/src/main/java/jtorrent/domain/Client.java @@ -85,6 +85,7 @@ public void shutdown() { localServiceDiscoveryManager.stop(); dhtManager.stop(); infoHashToTorrentHandler.values().forEach(TorrentHandler::stop); + torrentRepository.persistTorrents(); } public void addTorrent(TorrentMetadata torrentMetaData, String name, Path saveDirectory) { diff --git a/src/main/java/jtorrent/domain/torrent/repository/TorrentRepository.java b/src/main/java/jtorrent/domain/torrent/repository/TorrentRepository.java index 41e691f1..4be4e1b9 100644 --- a/src/main/java/jtorrent/domain/torrent/repository/TorrentRepository.java +++ b/src/main/java/jtorrent/domain/torrent/repository/TorrentRepository.java @@ -13,19 +13,21 @@ public interface TorrentRepository { - void addTorrent(Torrent torrent); + RxObservableList getTorrents(); - TorrentMetadata loadTorrent(File file) throws IOException; + Torrent getTorrent(Sha1Hash infoHash); - TorrentMetadata loadTorrent(URL url) throws IOException; + void addTorrent(Torrent torrent); - void saveTorrent(TorrentMetadata torrentMetadata, Path savePath) throws IOException; + void persistTorrents(); void removeTorrent(Torrent torrent); - RxObservableList getTorrents(); + TorrentMetadata loadTorrent(File file) throws IOException; - Torrent getTorrent(Sha1Hash infoHash); + TorrentMetadata loadTorrent(URL url) throws IOException; + + void saveTorrent(TorrentMetadata torrentMetadata, Path savePath) throws IOException; TorrentMetadata createNewTorrent(Path source, List> trackerUrls, String comment, String createdBy, int pieceSize) throws IOException; From b27d86105db8eecb33c4a14a4ef603cd99ca3d9e Mon Sep 17 00:00:00 2001 From: Ashuh Date: Mon, 13 May 2024 01:05:41 +0800 Subject: [PATCH 09/33] Override equals & hashcode in HttpTracker --- .../domain/tracker/model/http/HttpTracker.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/jtorrent/domain/tracker/model/http/HttpTracker.java b/src/main/java/jtorrent/domain/tracker/model/http/HttpTracker.java index 9b9675f5..f534d48a 100644 --- a/src/main/java/jtorrent/domain/tracker/model/http/HttpTracker.java +++ b/src/main/java/jtorrent/domain/tracker/model/http/HttpTracker.java @@ -88,4 +88,22 @@ public URI getUri() { private String encodeValue(String value) { return URLEncoder.encode(value, StandardCharsets.ISO_8859_1); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + HttpTracker that = (HttpTracker) o; + return uri.equals(that.uri); + } + + @Override + public int hashCode() { + return uri.hashCode(); + } } From c069c3c7d8425c60069d759b42a4638ca79d8d8d Mon Sep 17 00:00:00 2001 From: Ashuh Date: Fri, 24 May 2024 01:26:10 +0800 Subject: [PATCH 10/33] Override equals & hashcode in torrent entity models --- .../source/db/model/FileInfoComponent.java | 31 +++++++++++++++++++ .../db/model/FileMetadataComponent.java | 31 +++++++++++++++++++ .../db/model/FileProgressComponent.java | 18 +++++++++++ .../db/model/TorrentMetadataComponent.java | 27 ++++++++++++++++ .../db/model/TorrentProgressComponent.java | 23 ++++++++++++++ .../db/model/TorrentStatisticsComponent.java | 20 ++++++++++++ 6 files changed, 150 insertions(+) diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java index 399b595d..f180ece0 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java @@ -3,6 +3,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; import org.hibernate.annotations.Formula; @@ -131,6 +133,35 @@ public byte[] getInfoHash() { return infoHash; } + @Override + public int hashCode() { + int result = Objects.hashCode(directory); + result = 31 * result + fileMetadata.hashCode(); + result = 31 * result + pieceHashes.hashCode(); + result = 31 * result + pieceSize; + result = 31 * result + Arrays.hashCode(infoHash); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FileInfoComponent that = (FileInfoComponent) o; + return pieceSize == that.pieceSize + && Objects.equals(directory, that.directory) + && fileMetadata.equals(that.fileMetadata) + && pieceHashes.size() == that.pieceHashes.size() + && IntStream.range(0, pieceHashes.size()) + .allMatch(i -> Arrays.equals(pieceHashes.get(i), that.pieceHashes.get(i))) + && Arrays.equals(infoHash, that.infoHash); + } + @Override public String toString() { return "FileInfoComponent{" diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java index c0f5ecac..f1c221be 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java @@ -98,6 +98,37 @@ public long getStart() { return start; } + @Override + public int hashCode() { + int result = Long.hashCode(size); + result = 31 * result + path.hashCode(); + result = 31 * result + firstPiece; + result = 31 * result + firstPieceStart; + result = 31 * result + lastPiece; + result = 31 * result + lastPieceEnd; + result = 31 * result + Long.hashCode(start); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FileMetadataComponent that = (FileMetadataComponent) o; + return size == that.size + && firstPiece == that.firstPiece + && firstPieceStart == that.firstPieceStart + && lastPiece == that.lastPiece + && lastPieceEnd == that.lastPieceEnd + && start == that.start + && path.equals(that.path); + } + @Override public String toString() { return "FileMetadataComponent{" diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java index 71cbcba5..d28ce8fa 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java @@ -37,6 +37,24 @@ public byte[] getVerifiedPieces() { return verifiedPieces; } + @Override + public int hashCode() { + return Arrays.hashCode(verifiedPieces); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FileProgressComponent that = (FileProgressComponent) o; + return Arrays.equals(verifiedPieces, that.verifiedPieces); + } + @Override public String toString() { return "FileProgressComponent{" diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java index 3afd4e2f..f1e0f1f5 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java @@ -83,6 +83,33 @@ public FileInfoComponent getFileInfo() { return fileInfo; } + @Override + public int hashCode() { + int result = trackers.hashCode(); + result = 31 * result + creationDate.hashCode(); + result = 31 * result + comment.hashCode(); + result = 31 * result + createdBy.hashCode(); + result = 31 * result + fileInfo.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TorrentMetadataComponent that = (TorrentMetadataComponent) o; + return trackers.equals(that.trackers) + && creationDate.equals(that.creationDate) + && comment.equals(that.comment) + && createdBy.equals(that.createdBy) + && fileInfo.equals(that.fileInfo); + } + @Override public String toString() { return "TorrentMetadataComponent{" diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java index 83747d6b..7d523c8f 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java @@ -74,6 +74,29 @@ public byte[] getVerifiedPieces() { return verifiedPieces; } + @Override + public int hashCode() { + int result = pathToFileProgress.hashCode(); + result = 31 * result + pieceToReceivedBlocks.hashCode(); + result = 31 * result + Arrays.hashCode(verifiedPieces); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TorrentProgressComponent that = (TorrentProgressComponent) o; + return pathToFileProgress.equals(that.pathToFileProgress) + && pieceToReceivedBlocks.equals(that.pieceToReceivedBlocks) + && Arrays.equals(verifiedPieces, that.verifiedPieces); + } + @Override public String toString() { return "TorrentProgressComponent{" diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentStatisticsComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentStatisticsComponent.java index 15b3ce19..def06224 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentStatisticsComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentStatisticsComponent.java @@ -36,6 +36,26 @@ public long getUploaded() { return uploaded; } + @Override + public int hashCode() { + int result = Long.hashCode(downloaded); + result = 31 * result + Long.hashCode(uploaded); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TorrentStatisticsComponent that = (TorrentStatisticsComponent) o; + return downloaded == that.downloaded && uploaded == that.uploaded; + } + @Override public String toString() { return "TorrentStatisticsComponent{" From 9bf76e1daf3e23cf0303a01dca498930568750af Mon Sep 17 00:00:00 2001 From: Ashuh Date: Fri, 24 May 2024 01:26:38 +0800 Subject: [PATCH 11/33] Add argument validity check --- src/main/java/jtorrent/domain/torrent/model/FileInfo.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/jtorrent/domain/torrent/model/FileInfo.java b/src/main/java/jtorrent/domain/torrent/model/FileInfo.java index e5f7bba0..776a445e 100644 --- a/src/main/java/jtorrent/domain/torrent/model/FileInfo.java +++ b/src/main/java/jtorrent/domain/torrent/model/FileInfo.java @@ -87,6 +87,10 @@ public int getNumBlocks(int pieceIndex) { } public int getPieceSize(int piece) { + if (piece < 0 || piece >= getNumPieces()) { + throw new IllegalArgumentException("Invalid piece index: " + piece); + } + if (piece == getNumPieces() - 1) { int remainder = (int) (getTotalFileSize() % pieceSize); return remainder == 0 ? pieceSize : remainder; From 0f0f9dec04b14f3bb66225cd8ad8734263e34414 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Fri, 24 May 2024 02:57:18 +0800 Subject: [PATCH 12/33] Fix bug in TorrentProgress.createExisting Completely missing pieces are not stored in piece-block availability map --- .../domain/torrent/model/TorrentProgress.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java b/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java index 616a3cce..54b53393 100644 --- a/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java +++ b/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.subjects.BehaviorSubject; @@ -83,15 +84,23 @@ public static TorrentProgress createExisting(FileInfo fileInfo, Map { - if (availableBlocks.cardinality() == fileInfo.getNumBlocks(piece)) { - completePieces.set(piece); - } else if (availableBlocks.cardinality() > 0) { - partiallyMissingPieces.set(piece); - } else { - completelyMissingPieces.set(piece); - } - }); + IntStream.range(0, fileInfo.getNumPieces()) + .forEach(piece -> { + final int numAvailableBlocks; + if (pieceIndexToAvailableBlocks.containsKey(piece)) { + numAvailableBlocks = pieceIndexToAvailableBlocks.get(piece).cardinality(); + } else { + numAvailableBlocks = 0; + } + + if (numAvailableBlocks == fileInfo.getNumBlocks(piece)) { + completePieces.set(piece); + } else if (numAvailableBlocks > 0) { + partiallyMissingPieces.set(piece); + } else { + completelyMissingPieces.set(piece); + } + }); BitSet partiallyMissingPiecesWithUnrequestedBlocks = (BitSet) partiallyMissingPieces.clone(); BitSet completelyMissingPiecesWithUnrequestedBlocks = (BitSet) completelyMissingPieces.clone(); From 7a11d57fac8e42032ce26c5a1a0348c8ca3e4a51 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Fri, 24 May 2024 03:01:17 +0800 Subject: [PATCH 13/33] Remove FileProgressComponent File progress can be derived from other sources when mapping to domain --- .../db/model/FileProgressComponent.java | 64 ------------------- .../db/model/TorrentProgressComponent.java | 44 +++++-------- .../domain/torrent/model/FileProgress.java | 6 ++ 3 files changed, 22 insertions(+), 92 deletions(-) delete mode 100644 src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java deleted file mode 100644 index d28ce8fa..00000000 --- a/src/main/java/jtorrent/data/torrent/source/db/model/FileProgressComponent.java +++ /dev/null @@ -1,64 +0,0 @@ -package jtorrent.data.torrent.source.db.model; - -import java.util.Arrays; -import java.util.BitSet; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import jtorrent.domain.torrent.model.FileInfo; -import jtorrent.domain.torrent.model.FileMetadata; -import jtorrent.domain.torrent.model.FileProgress; - -@Embeddable -public class FileProgressComponent { - - @Column(nullable = false) - private final byte[] verifiedPieces; - - protected FileProgressComponent() { - this(new byte[0]); - } - - public FileProgressComponent(byte[] verifiedPieces) { - this.verifiedPieces = verifiedPieces; - } - - public static FileProgressComponent fromDomain(FileProgress fileProgress) { - byte[] verifiedPieces = fileProgress.getVerifiedPieces().toByteArray(); - return new FileProgressComponent(verifiedPieces); - } - - public FileProgress toDomain(FileInfo fileInfo, FileMetadata fileMetadata) { - BitSet domainVerifiedPieces = BitSet.valueOf(verifiedPieces); - return FileProgress.createExisting(fileInfo, fileMetadata, domainVerifiedPieces); - } - - public byte[] getVerifiedPieces() { - return verifiedPieces; - } - - @Override - public int hashCode() { - return Arrays.hashCode(verifiedPieces); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - FileProgressComponent that = (FileProgressComponent) o; - return Arrays.equals(verifiedPieces, that.verifiedPieces); - } - - @Override - public String toString() { - return "FileProgressComponent{" - + ", verifiedPieces=" + Arrays.toString(verifiedPieces) - + '}'; - } -} diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java index 7d523c8f..681ab6ed 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java @@ -5,6 +5,7 @@ import java.util.BitSet; import java.util.Collections; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; import jakarta.persistence.CollectionTable; @@ -12,16 +13,13 @@ import jakarta.persistence.ElementCollection; import jakarta.persistence.Embeddable; import jtorrent.domain.torrent.model.FileInfo; +import jtorrent.domain.torrent.model.FileMetadata; import jtorrent.domain.torrent.model.FileProgress; import jtorrent.domain.torrent.model.TorrentProgress; @Embeddable public class TorrentProgressComponent { - @ElementCollection - @CollectionTable - private final Map pathToFileProgress; - @ElementCollection @CollectionTable private final Map pieceToReceivedBlocks; @@ -30,42 +28,35 @@ public class TorrentProgressComponent { private final byte[] verifiedPieces; protected TorrentProgressComponent() { - this(Collections.emptyMap(), Collections.emptyMap(), new byte[0]); + this(Collections.emptyMap(), new byte[0]); } - public TorrentProgressComponent(Map pathToFileProgress, - Map pieceToReceivedBlocks, byte[] verifiedPieces) { - this.pathToFileProgress = pathToFileProgress; + public TorrentProgressComponent(Map pieceToReceivedBlocks, byte[] verifiedPieces) { this.pieceToReceivedBlocks = pieceToReceivedBlocks; this.verifiedPieces = verifiedPieces; } public static TorrentProgressComponent fromDomain(TorrentProgress torrentProgress) { byte[] verifiedPieces = torrentProgress.getVerifiedPieces().toByteArray(); - Map pathToFileProgress = torrentProgress.getFileProgress().entrySet().stream() - .map(e -> Map.entry(e.getKey().toString(), FileProgressComponent.fromDomain(e.getValue()))) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); Map pieceToReceivedBlocks = torrentProgress.getReceivedBlocks(); - return new TorrentProgressComponent(pathToFileProgress, pieceToReceivedBlocks, verifiedPieces); + return new TorrentProgressComponent(pieceToReceivedBlocks, verifiedPieces); } public TorrentProgress toDomain(FileInfo fileInfo) { - Map domainFileProgress = pathToFileProgress.entrySet().stream() - .map(e -> { - Path path = Path.of(e.getKey()); - FileProgress fileProgress = e.getValue().toDomain(fileInfo, fileInfo.getFileMetaData(path)); - return Map.entry(path, fileProgress); - }) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); BitSet domainVerifiedPieces = BitSet.valueOf(verifiedPieces); + Map domainFileProgress = fileInfo.getFileMetaData().stream() + .map(FileMetadata::path) + .collect( + Collectors.toMap( + Function.identity(), + path -> FileProgress.createExisting(fileInfo, fileInfo.getFileMetaData(path), + domainVerifiedPieces) + ) + ); return TorrentProgress.createExisting(fileInfo, domainFileProgress, domainVerifiedPieces, pieceToReceivedBlocks); } - public Map getPathToFileProgress() { - return pathToFileProgress; - } - public Map getPieceToReceivedBlocks() { return pieceToReceivedBlocks; } @@ -76,8 +67,7 @@ public byte[] getVerifiedPieces() { @Override public int hashCode() { - int result = pathToFileProgress.hashCode(); - result = 31 * result + pieceToReceivedBlocks.hashCode(); + int result = pieceToReceivedBlocks.hashCode(); result = 31 * result + Arrays.hashCode(verifiedPieces); return result; } @@ -92,15 +82,13 @@ public boolean equals(Object o) { } TorrentProgressComponent that = (TorrentProgressComponent) o; - return pathToFileProgress.equals(that.pathToFileProgress) - && pieceToReceivedBlocks.equals(that.pieceToReceivedBlocks) + return pieceToReceivedBlocks.equals(that.pieceToReceivedBlocks) && Arrays.equals(verifiedPieces, that.verifiedPieces); } @Override public String toString() { return "TorrentProgressComponent{" - + "pathToFileProgress=" + pathToFileProgress + ", pieceToReceivedBlocks=" + pieceToReceivedBlocks + ", verifiedPieces=" + Arrays.toString(verifiedPieces) + '}'; diff --git a/src/main/java/jtorrent/domain/torrent/model/FileProgress.java b/src/main/java/jtorrent/domain/torrent/model/FileProgress.java index 5c269343..c555d061 100644 --- a/src/main/java/jtorrent/domain/torrent/model/FileProgress.java +++ b/src/main/java/jtorrent/domain/torrent/model/FileProgress.java @@ -31,6 +31,12 @@ public static FileProgress createNew(FileInfo fileInfo, FileMetadata fileMetaDat return new FileProgress(fileInfo, fileMetaData, 0, new BitSet()); } + /** + * @param fileInfo the file info for the torrent + * @param fileMetaData the metadata for the file + * @param verifiedPieces a bitset containing the verified pieces indices for the entire torrent, i.e., + * indices are global, not relative to the file + */ public static FileProgress createExisting(FileInfo fileInfo, FileMetadata fileMetaData, BitSet verifiedPieces) { long verifiedBytes = IntStream.range(fileMetaData.firstPiece(), fileMetaData.lastPiece() + 1) From 832718c1f6b738b025c5cb1f07c50ce38feac0e3 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Fri, 24 May 2024 03:08:10 +0800 Subject: [PATCH 14/33] Add tests --- build.gradle | 1 + .../source/db/model/TorrentEntityTest.java | 185 ++++++++++++++++++ .../model/TorrentProgressComponentTest.java | 82 ++++++++ .../source/db/model/testutil/TestUtil.java | 23 +++ src/test/resources/instancio.properties | 1 + 5 files changed, 292 insertions(+) create mode 100644 src/test/java/jtorrent/data/torrent/source/db/model/TorrentEntityTest.java create mode 100644 src/test/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponentTest.java create mode 100644 src/test/java/jtorrent/data/torrent/source/db/model/testutil/TestUtil.java create mode 100644 src/test/resources/instancio.properties diff --git a/build.gradle b/build.gradle index 912ea318..551939ea 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' + testImplementation 'org.instancio:instancio-junit:4.5.1' } test { diff --git a/src/test/java/jtorrent/data/torrent/source/db/model/TorrentEntityTest.java b/src/test/java/jtorrent/data/torrent/source/db/model/TorrentEntityTest.java new file mode 100644 index 00000000..ecd1a0df --- /dev/null +++ b/src/test/java/jtorrent/data/torrent/source/db/model/TorrentEntityTest.java @@ -0,0 +1,185 @@ +package jtorrent.data.torrent.source.db.model; + +import static jtorrent.data.torrent.source.db.model.testutil.TestUtil.createBitSetWithRange; +import static org.instancio.Assign.valueOf; +import static org.instancio.Select.all; +import static org.instancio.Select.field; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.URI; +import java.nio.file.Path; +import java.util.BitSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import org.instancio.Instancio; +import org.instancio.Model; +import org.junit.jupiter.api.Test; + +import jtorrent.domain.common.util.rx.MutableRxObservableSet; +import jtorrent.domain.torrent.model.FileInfo; +import jtorrent.domain.torrent.model.FileMetadata; +import jtorrent.domain.torrent.model.FileProgress; +import jtorrent.domain.torrent.model.MultiFileInfo; +import jtorrent.domain.torrent.model.SingleFileInfo; +import jtorrent.domain.torrent.model.Torrent; +import jtorrent.domain.torrent.model.TorrentMetadata; +import jtorrent.domain.torrent.model.TorrentProgress; +import jtorrent.domain.tracker.model.factory.TrackerFactory; + +class TorrentEntityTest { + + private static final Model TORRENT_PROGRESS_MODEL = Instancio.of(TorrentProgress.class) + .set(field("verifiedPieces"), new BitSet()) + .set(field("completePieces"), new BitSet()) + .set(field("pieceIndexToRequestedBlocks"), Map.of()) + .set(field("pieceIndexToAvailableBlocks"), Map.of()) + .set(field("partiallyMissingPieces"), new BitSet()) + .set(field("partiallyMissingPiecesWithUnrequestedBlocks"), new BitSet()) + .set(field("completelyMissingPieces"), new BitSet()) + .set(field("completelyMissingPiecesWithUnrequestedBlocks"), new BitSet()) + .set(field("verifiedBytes"), new AtomicLong(0)) + .set(field("checkedBytes"), 0) + .toModel(); + + private static final Model FILE_PROGRESS_MODEL = Instancio.of(FileProgress.class) + .set(field("verifiedPieces"), new BitSet()) + .set(field("verifiedBytes"), new AtomicLong(0)) + .toModel(); + + @Test + void bidirectionalMapping_singleFile() { + FileMetadata fileMetadata = Instancio.of(FileMetadata.class) + .set(field("size"), 100) + .set(field("path"), Path.of("file.txt")) + .set(field("firstPiece"), 0) + .set(field("firstPieceStart"), 0) + .set(field("lastPiece"), 9) + .set(field("lastPieceEnd"), 9) + .set(field("start"), 0) + .set(field("end"), 99) + .create(); + + SingleFileInfo fileInfo = Instancio.of(SingleFileInfo.class) + .generate(all(byte[].class), gen -> gen.array().length(20)) + .generate(field(FileInfo.class, "pieceHashes"), gen -> gen.collection().size(10)) + .set(field(FileInfo.class, "pieceSize"), 10) + .set(field(FileInfo.class, "fileMetaData"), List.of(fileMetadata)) + .create(); + + Torrent expected = Instancio.of(Torrent.class) + .assign(valueOf(field(TorrentMetadata.class, "trackers")) + .to(field(Torrent.class, "trackers")) + .as(trackers -> ((Set) trackers) + .stream() + .map(TrackerFactory::fromUri) + .collect(Collectors.toSet()) + ) + ) + .set(field("peers"), new MutableRxObservableSet<>(Set.of())) + .set(field(TorrentMetadata.class, "fileInfo"), fileInfo) + .set(field("torrentProgress"), + Instancio.of(TORRENT_PROGRESS_MODEL) + .set(field("fileInfo"), fileInfo) + .set(field("completelyMissingPieces"), createBitSetWithRange(0, 10)) + .set(field("completelyMissingPiecesWithUnrequestedBlocks"), + createBitSetWithRange(0, 10)) + .set(field("pathToFileProgress"), + Map.of( + Path.of("file.txt"), + Instancio.of(FILE_PROGRESS_MODEL) + .set(field("fileInfo"), fileInfo) + .set(field("fileMetaData"), fileMetadata) + .create() + ) + ) + .create() + ) + .create(); + + TorrentEntity torrentEntity = TorrentEntity.fromDomain(expected); + Torrent actual = torrentEntity.toDomain(); + + assertEquals(expected, actual); + } + + @Test + void bidirectionalMapping_multiFile() { + FileMetadata fileMetadata1 = Instancio.of(FileMetadata.class) + .set(field("size"), 100) + .set(field("path"), Path.of("file1.txt")) + .set(field("firstPiece"), 0) + .set(field("firstPieceStart"), 0) + .set(field("lastPiece"), 9) + .set(field("lastPieceEnd"), 9) + .set(field("start"), 0) + .set(field("end"), 99) + .create(); + + FileMetadata fileMetadata2 = Instancio.of(FileMetadata.class) + .set(field("size"), 100) + .set(field("path"), Path.of("file2.txt")) + .set(field("firstPiece"), 10) + .set(field("firstPieceStart"), 0) + .set(field("lastPiece"), 19) + .set(field("lastPieceEnd"), 9) + .set(field("start"), 100) + .set(field("end"), 199) + .create(); + + MultiFileInfo fileInfo = Instancio.of(MultiFileInfo.class) + .generate(all(byte[].class), gen -> gen.array().length(20)) + .generate(field(FileInfo.class, "pieceHashes"), gen -> gen.collection().size(20)) + .set(field(FileInfo.class, "pieceSize"), 10) + .set(field(FileInfo.class, "fileMetaData"), + List.of( + fileMetadata1, + fileMetadata2 + ) + ) + .create(); + + Torrent expected = Instancio.of(Torrent.class) + .assign(valueOf(field(TorrentMetadata.class, "trackers")) + .to(field(Torrent.class, "trackers")) + .as(trackers -> ((Set) trackers) + .stream() + .map(TrackerFactory::fromUri) + .collect(Collectors.toSet()) + ) + ) + .set(field("peers"), new MutableRxObservableSet<>(Set.of())) + .set(field(TorrentMetadata.class, "fileInfo"), fileInfo) + .set(field("torrentProgress"), + Instancio.of(TORRENT_PROGRESS_MODEL) + .set(field("fileInfo"), fileInfo) + .set(field("completelyMissingPieces"), createBitSetWithRange(0, 20)) + .set(field("completelyMissingPiecesWithUnrequestedBlocks"), + createBitSetWithRange(0, 20)) + .set(field("pathToFileProgress"), + Map.of( + Path.of("file1.txt"), + Instancio.of(FILE_PROGRESS_MODEL) + .set(field("fileInfo"), fileInfo) + .set(field("fileMetaData"), fileMetadata1) + .create(), + Path.of("file2.txt"), + Instancio.of(FILE_PROGRESS_MODEL) + .set(field("fileInfo"), fileInfo) + .set(field("fileMetaData"), fileMetadata2) + .create() + ) + ) + .create() + ) + .create(); + + TorrentEntity torrentEntity = TorrentEntity.fromDomain(expected); + Torrent actual = torrentEntity.toDomain(); + + assertEquals(expected, actual); + } +} diff --git a/src/test/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponentTest.java b/src/test/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponentTest.java new file mode 100644 index 00000000..1662ab8c --- /dev/null +++ b/src/test/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponentTest.java @@ -0,0 +1,82 @@ +package jtorrent.data.torrent.source.db.model; + +import static jtorrent.data.torrent.source.db.model.testutil.TestUtil.createBitSet; +import static org.instancio.Select.all; +import static org.instancio.Select.field; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.util.BitSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +import org.instancio.Instancio; +import org.junit.jupiter.api.Test; + +import jtorrent.domain.torrent.model.FileInfo; +import jtorrent.domain.torrent.model.FileMetadata; +import jtorrent.domain.torrent.model.FileProgress; +import jtorrent.domain.torrent.model.SingleFileInfo; +import jtorrent.domain.torrent.model.TorrentProgress; + +class TorrentProgressComponentTest { + + @Test + void bidirectionalMapping() { + FileMetadata fileMetadata = Instancio.of(FileMetadata.class) + .set(field("size"), 49) + .set(field("path"), Path.of("file.txt")) + .set(field("firstPiece"), 0) + .set(field("firstPieceStart"), 0) + .set(field("lastPiece"), 4) + .set(field("lastPieceEnd"), 8) + .set(field("start"), 0) + .set(field("end"), 48) + .create(); + + SingleFileInfo fileInfo = Instancio.of(SingleFileInfo.class) + .generate(all(byte[].class), gen -> gen.array().length(20)) + .set(field(FileInfo.class, "pieceSize"), 10) + .generate(field(FileInfo.class, "pieceHashes"), gen -> gen.collection().size(5)) + .set(field(FileInfo.class, "fileMetaData"), List.of(fileMetadata)) + .create(); + + TorrentProgress expected = Instancio.of(TorrentProgress.class) + .set(field("fileInfo"), fileInfo) + .set(field("pathToFileProgress"), + Map.of( + Path.of("file.txt"), + Instancio.of(FileProgress.class) + .set(field("verifiedPieces"), createBitSet(0, 1, 2, 4)) + .set(field("verifiedBytes"), new AtomicLong(39)) + .set(field("fileInfo"), fileInfo) + .set(field("fileMetaData"), fileMetadata) + .create() + ) + ) + .set(field("verifiedPieces"), createBitSet(0, 1, 2, 4)) + .set(field("completePieces"), createBitSet(0, 1, 2, 4)) + .set(field("pieceIndexToRequestedBlocks"), Map.of()) + .set(field("pieceIndexToAvailableBlocks"), + Map.of( + 0, createBitSet(0), + 1, createBitSet(0), + 2, createBitSet(0), + 4, createBitSet(0) + ) + ) + .set(field("partiallyMissingPieces"), new BitSet()) + .set(field("partiallyMissingPiecesWithUnrequestedBlocks"), new BitSet()) + .set(field("completelyMissingPieces"), createBitSet(3)) + .set(field("completelyMissingPiecesWithUnrequestedBlocks"), createBitSet(3)) + .set(field("verifiedBytes"), new AtomicLong(39)) + .set(field("checkedBytes"), 0) + .create(); + + TorrentProgressComponent torrentProgressComponent = TorrentProgressComponent.fromDomain(expected); + TorrentProgress actual = torrentProgressComponent.toDomain(fileInfo); + + assertEquals(expected, actual); + } +} diff --git a/src/test/java/jtorrent/data/torrent/source/db/model/testutil/TestUtil.java b/src/test/java/jtorrent/data/torrent/source/db/model/testutil/TestUtil.java new file mode 100644 index 00000000..cf0625fc --- /dev/null +++ b/src/test/java/jtorrent/data/torrent/source/db/model/testutil/TestUtil.java @@ -0,0 +1,23 @@ +package jtorrent.data.torrent.source.db.model.testutil; + +import java.util.BitSet; + +public class TestUtil { + + private TestUtil() { + } + + public static BitSet createBitSet(int... bits) { + BitSet bitSet = new BitSet(); + for (int bit : bits) { + bitSet.set(bit); + } + return bitSet; + } + + public static BitSet createBitSetWithRange(int from, int to) { + BitSet bitSet = new BitSet(); + bitSet.set(from, to); + return bitSet; + } +} diff --git a/src/test/resources/instancio.properties b/src/test/resources/instancio.properties new file mode 100644 index 00000000..ff2429e6 --- /dev/null +++ b/src/test/resources/instancio.properties @@ -0,0 +1 @@ +seed = 1 From 11c566911b4165f66bdb5b53db5fa6dc48b3b09e Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sat, 25 May 2024 05:31:09 +0800 Subject: [PATCH 15/33] Move bencoded models to new package --- .../torrent/repository/FileTorrentRepository.java | 10 +++++----- .../{ => source/file}/model/BencodedFile.java | 4 ++-- .../{ => source/file}/model/BencodedInfo.java | 2 +- .../{ => source/file}/model/BencodedInfoFactory.java | 6 +++--- .../file}/model/BencodedMultiFileInfo.java | 4 ++-- .../file}/model/BencodedSingleFileInfo.java | 4 ++-- .../{ => source/file}/model/BencodedTorrent.java | 12 ++++++------ .../file}/model/exception/MappingException.java | 2 +- .../{ => source/file}/model/util/MapUtil.java | 2 +- .../{ => source/file}/model/BencodedTorrentTest.java | 2 +- 10 files changed, 24 insertions(+), 24 deletions(-) rename src/main/java/jtorrent/data/torrent/{ => source/file}/model/BencodedFile.java (96%) rename src/main/java/jtorrent/data/torrent/{ => source/file}/model/BencodedInfo.java (97%) rename src/main/java/jtorrent/data/torrent/{ => source/file}/model/BencodedInfoFactory.java (81%) rename src/main/java/jtorrent/data/torrent/{ => source/file}/model/BencodedMultiFileInfo.java (97%) rename src/main/java/jtorrent/data/torrent/{ => source/file}/model/BencodedSingleFileInfo.java (96%) rename src/main/java/jtorrent/data/torrent/{ => source/file}/model/BencodedTorrent.java (94%) rename src/main/java/jtorrent/data/torrent/{ => source/file}/model/exception/MappingException.java (80%) rename src/main/java/jtorrent/data/torrent/{ => source/file}/model/util/MapUtil.java (95%) rename src/test/java/jtorrent/data/torrent/{ => source/file}/model/BencodedTorrentTest.java (99%) diff --git a/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java b/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java index 66c04dab..603b6d45 100644 --- a/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java +++ b/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java @@ -20,13 +20,13 @@ import java.util.Map; import java.util.stream.Stream; -import jtorrent.data.torrent.model.BencodedFile; -import jtorrent.data.torrent.model.BencodedInfo; -import jtorrent.data.torrent.model.BencodedMultiFileInfo; -import jtorrent.data.torrent.model.BencodedSingleFileInfo; -import jtorrent.data.torrent.model.BencodedTorrent; import jtorrent.data.torrent.source.db.dao.TorrentDao; import jtorrent.data.torrent.source.db.model.TorrentEntity; +import jtorrent.data.torrent.source.file.model.BencodedFile; +import jtorrent.data.torrent.source.file.model.BencodedInfo; +import jtorrent.data.torrent.source.file.model.BencodedMultiFileInfo; +import jtorrent.data.torrent.source.file.model.BencodedSingleFileInfo; +import jtorrent.data.torrent.source.file.model.BencodedTorrent; import jtorrent.domain.common.util.ContinuousMergedInputStream; import jtorrent.domain.common.util.Sha1Hash; import jtorrent.domain.common.util.rx.MutableRxObservableList; diff --git a/src/main/java/jtorrent/data/torrent/model/BencodedFile.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedFile.java similarity index 96% rename from src/main/java/jtorrent/data/torrent/model/BencodedFile.java rename to src/main/java/jtorrent/data/torrent/source/file/model/BencodedFile.java index 77e66ad2..2f75c19e 100644 --- a/src/main/java/jtorrent/data/torrent/model/BencodedFile.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedFile.java @@ -1,4 +1,4 @@ -package jtorrent.data.torrent.model; +package jtorrent.data.torrent.source.file.model; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -8,7 +8,7 @@ import java.util.Objects; import java.util.stream.Collectors; -import jtorrent.data.torrent.model.util.MapUtil; +import jtorrent.data.torrent.source.file.model.util.MapUtil; import jtorrent.domain.common.util.bencode.BencodedObject; import jtorrent.domain.torrent.model.FileMetadata; diff --git a/src/main/java/jtorrent/data/torrent/model/BencodedInfo.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedInfo.java similarity index 97% rename from src/main/java/jtorrent/data/torrent/model/BencodedInfo.java rename to src/main/java/jtorrent/data/torrent/source/file/model/BencodedInfo.java index a0de789f..b5c44f8b 100644 --- a/src/main/java/jtorrent/data/torrent/model/BencodedInfo.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedInfo.java @@ -1,4 +1,4 @@ -package jtorrent.data.torrent.model; +package jtorrent.data.torrent.source.file.model; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; diff --git a/src/main/java/jtorrent/data/torrent/model/BencodedInfoFactory.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedInfoFactory.java similarity index 81% rename from src/main/java/jtorrent/data/torrent/model/BencodedInfoFactory.java rename to src/main/java/jtorrent/data/torrent/source/file/model/BencodedInfoFactory.java index 7961a4a4..d826c232 100644 --- a/src/main/java/jtorrent/data/torrent/model/BencodedInfoFactory.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedInfoFactory.java @@ -1,7 +1,7 @@ -package jtorrent.data.torrent.model; +package jtorrent.data.torrent.source.file.model; -import static jtorrent.data.torrent.model.BencodedInfo.KEY_FILES; -import static jtorrent.data.torrent.model.BencodedInfo.KEY_LENGTH; +import static jtorrent.data.torrent.source.file.model.BencodedInfo.KEY_FILES; +import static jtorrent.data.torrent.source.file.model.BencodedInfo.KEY_LENGTH; import java.util.Map; diff --git a/src/main/java/jtorrent/data/torrent/model/BencodedMultiFileInfo.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java similarity index 97% rename from src/main/java/jtorrent/data/torrent/model/BencodedMultiFileInfo.java rename to src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java index c0012653..a786149c 100644 --- a/src/main/java/jtorrent/data/torrent/model/BencodedMultiFileInfo.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java @@ -1,4 +1,4 @@ -package jtorrent.data.torrent.model; +package jtorrent.data.torrent.source.file.model; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -9,7 +9,7 @@ import java.util.Objects; import java.util.stream.Collectors; -import jtorrent.data.torrent.model.util.MapUtil; +import jtorrent.data.torrent.source.file.model.util.MapUtil; import jtorrent.domain.common.util.Sha1Hash; import jtorrent.domain.torrent.model.FileInfo; import jtorrent.domain.torrent.model.FileMetadata; diff --git a/src/main/java/jtorrent/data/torrent/model/BencodedSingleFileInfo.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java similarity index 96% rename from src/main/java/jtorrent/data/torrent/model/BencodedSingleFileInfo.java rename to src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java index fc097925..8666f1d1 100644 --- a/src/main/java/jtorrent/data/torrent/model/BencodedSingleFileInfo.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java @@ -1,4 +1,4 @@ -package jtorrent.data.torrent.model; +package jtorrent.data.torrent.source.file.model; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -7,7 +7,7 @@ import java.util.Map; import java.util.Objects; -import jtorrent.data.torrent.model.util.MapUtil; +import jtorrent.data.torrent.source.file.model.util.MapUtil; import jtorrent.domain.common.util.Sha1Hash; import jtorrent.domain.torrent.model.FileInfo; import jtorrent.domain.torrent.model.FileMetadata; diff --git a/src/main/java/jtorrent/data/torrent/model/BencodedTorrent.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrent.java similarity index 94% rename from src/main/java/jtorrent/data/torrent/model/BencodedTorrent.java rename to src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrent.java index 4c3f1d42..7e85acd2 100644 --- a/src/main/java/jtorrent/data/torrent/model/BencodedTorrent.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrent.java @@ -1,9 +1,9 @@ -package jtorrent.data.torrent.model; +package jtorrent.data.torrent.source.file.model; -import static jtorrent.data.torrent.model.util.MapUtil.getValueAsList; -import static jtorrent.data.torrent.model.util.MapUtil.getValueAsLong; -import static jtorrent.data.torrent.model.util.MapUtil.getValueAsMap; -import static jtorrent.data.torrent.model.util.MapUtil.getValueAsString; +import static jtorrent.data.torrent.source.file.model.util.MapUtil.getValueAsList; +import static jtorrent.data.torrent.source.file.model.util.MapUtil.getValueAsLong; +import static jtorrent.data.torrent.source.file.model.util.MapUtil.getValueAsMap; +import static jtorrent.data.torrent.source.file.model.util.MapUtil.getValueAsString; import java.io.IOException; import java.io.InputStream; @@ -22,7 +22,7 @@ import com.dampcake.bencode.BencodeInputStream; -import jtorrent.data.torrent.model.exception.MappingException; +import jtorrent.data.torrent.source.file.model.exception.MappingException; import jtorrent.domain.common.util.bencode.BencodedObject; import jtorrent.domain.torrent.model.FileInfo; import jtorrent.domain.torrent.model.TorrentMetadata; diff --git a/src/main/java/jtorrent/data/torrent/model/exception/MappingException.java b/src/main/java/jtorrent/data/torrent/source/file/model/exception/MappingException.java similarity index 80% rename from src/main/java/jtorrent/data/torrent/model/exception/MappingException.java rename to src/main/java/jtorrent/data/torrent/source/file/model/exception/MappingException.java index e05b8372..cc52544f 100644 --- a/src/main/java/jtorrent/data/torrent/model/exception/MappingException.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/exception/MappingException.java @@ -1,4 +1,4 @@ -package jtorrent.data.torrent.model.exception; +package jtorrent.data.torrent.source.file.model.exception; public class MappingException extends RuntimeException { diff --git a/src/main/java/jtorrent/data/torrent/model/util/MapUtil.java b/src/main/java/jtorrent/data/torrent/source/file/model/util/MapUtil.java similarity index 95% rename from src/main/java/jtorrent/data/torrent/model/util/MapUtil.java rename to src/main/java/jtorrent/data/torrent/source/file/model/util/MapUtil.java index 55bf3f3d..b748d78b 100644 --- a/src/main/java/jtorrent/data/torrent/model/util/MapUtil.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/util/MapUtil.java @@ -1,4 +1,4 @@ -package jtorrent.data.torrent.model.util; +package jtorrent.data.torrent.source.file.model.util; import java.nio.ByteBuffer; import java.util.Collections; diff --git a/src/test/java/jtorrent/data/torrent/model/BencodedTorrentTest.java b/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java similarity index 99% rename from src/test/java/jtorrent/data/torrent/model/BencodedTorrentTest.java rename to src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java index d2e276e5..9f2571b4 100644 --- a/src/test/java/jtorrent/data/torrent/model/BencodedTorrentTest.java +++ b/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java @@ -1,4 +1,4 @@ -package jtorrent.data.torrent.model; +package jtorrent.data.torrent.source.file.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; From 59da535fbd3aca16da654401cc3828a50d6905f0 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sat, 25 May 2024 23:01:30 +0800 Subject: [PATCH 16/33] Refactor repository classes --- ...epository.java => AppPieceRepository.java} | 4 +- .../AppTorrentMetadataRepository.java | 49 +++++ .../repository/AppTorrentRepository.java | 70 ++++++ .../repository/FileTorrentRepository.java | 202 ------------------ .../BencodedTorrentFileManager.java | 68 ++++++ .../file/model/BencodedTorrentFactory.java | 84 ++++++++ src/main/java/jtorrent/domain/Client.java | 19 +- .../repository/TorrentMetadataRepository.java | 21 ++ .../torrent/repository/TorrentRepository.java | 16 -- .../java/jtorrent/presentation/JTorrent.java | 15 +- .../main/viewmodel/MainViewModel.java | 4 +- .../viewmodel/TorrentControlsViewModel.java | 4 +- 12 files changed, 318 insertions(+), 238 deletions(-) rename src/main/java/jtorrent/data/torrent/repository/{FilePieceRepository.java => AppPieceRepository.java} (97%) create mode 100644 src/main/java/jtorrent/data/torrent/repository/AppTorrentMetadataRepository.java create mode 100644 src/main/java/jtorrent/data/torrent/repository/AppTorrentRepository.java delete mode 100644 src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java create mode 100644 src/main/java/jtorrent/data/torrent/source/file/filemanager/BencodedTorrentFileManager.java create mode 100644 src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrentFactory.java create mode 100644 src/main/java/jtorrent/domain/torrent/repository/TorrentMetadataRepository.java diff --git a/src/main/java/jtorrent/data/torrent/repository/FilePieceRepository.java b/src/main/java/jtorrent/data/torrent/repository/AppPieceRepository.java similarity index 97% rename from src/main/java/jtorrent/data/torrent/repository/FilePieceRepository.java rename to src/main/java/jtorrent/data/torrent/repository/AppPieceRepository.java index 5a22ed6f..3ed8e497 100644 --- a/src/main/java/jtorrent/data/torrent/repository/FilePieceRepository.java +++ b/src/main/java/jtorrent/data/torrent/repository/AppPieceRepository.java @@ -15,9 +15,9 @@ import jtorrent.domain.torrent.model.Torrent; import jtorrent.domain.torrent.repository.PieceRepository; -public class FilePieceRepository implements PieceRepository { +public class AppPieceRepository implements PieceRepository { - private static final Logger LOGGER = LoggerFactory.getLogger(FilePieceRepository.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AppPieceRepository.class); private static final String READ_ONLY_MODE = "r"; private static final String READ_WRITE_MODE = "rw"; diff --git a/src/main/java/jtorrent/data/torrent/repository/AppTorrentMetadataRepository.java b/src/main/java/jtorrent/data/torrent/repository/AppTorrentMetadataRepository.java new file mode 100644 index 00000000..212321da --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/repository/AppTorrentMetadataRepository.java @@ -0,0 +1,49 @@ +package jtorrent.data.torrent.repository; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Path; +import java.util.List; + +import jtorrent.data.torrent.source.file.filemanager.BencodedTorrentFileManager; +import jtorrent.data.torrent.source.file.model.BencodedTorrent; +import jtorrent.data.torrent.source.file.model.BencodedTorrentFactory; +import jtorrent.domain.torrent.model.TorrentMetadata; +import jtorrent.domain.torrent.repository.TorrentMetadataRepository; + +public class AppTorrentMetadataRepository implements TorrentMetadataRepository { + + private final BencodedTorrentFileManager torrentFileManager = new BencodedTorrentFileManager(); + + @Override + public TorrentMetadata getTorrentMetadata(File file) throws IOException { + return torrentFileManager.read(file).toDomain(); + } + + @Override + public TorrentMetadata getTorrentMetadata(URL url) throws IOException { + return torrentFileManager.read(url).toDomain(); + } + + @Override + public void saveTorrentMetadata(TorrentMetadata torrentMetadata, Path savePath) throws IOException { + BencodedTorrent bencodedTorrent = BencodedTorrent.fromDomain(torrentMetadata); + torrentFileManager.write(savePath, bencodedTorrent); + } + + /** + * Create a new {@link TorrentMetadata} instance with the current time as the creation date. + * + * @param trackerUrls list of tiers, each containing a list of tracker URLs + * @param comment comment about the torrent + * @param createdBy name and version of the program used to create the .torrent + * @param pieceSize size of each piece in bytes + * @return a new {@link TorrentMetadata} instance + */ + @Override + public TorrentMetadata createNewTorrent(Path source, List> trackerUrls, String comment, + String createdBy, int pieceSize) throws IOException { + return BencodedTorrentFactory.create(source, trackerUrls, comment, createdBy, pieceSize).toDomain(); + } +} diff --git a/src/main/java/jtorrent/data/torrent/repository/AppTorrentRepository.java b/src/main/java/jtorrent/data/torrent/repository/AppTorrentRepository.java new file mode 100644 index 00000000..c4ac3bb6 --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/repository/AppTorrentRepository.java @@ -0,0 +1,70 @@ +package jtorrent.data.torrent.repository; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jtorrent.data.torrent.source.db.dao.TorrentDao; +import jtorrent.data.torrent.source.db.model.TorrentEntity; +import jtorrent.domain.common.util.Sha1Hash; +import jtorrent.domain.common.util.rx.MutableRxObservableList; +import jtorrent.domain.common.util.rx.RxObservableList; +import jtorrent.domain.torrent.model.Torrent; +import jtorrent.domain.torrent.repository.TorrentRepository; + +public class AppTorrentRepository implements TorrentRepository { + + private final MutableRxObservableList torrentsObservable; + private final Map infoHashToTorrent; + private final TorrentDao torrentDao = new TorrentDao(); + + public AppTorrentRepository() { + List torrents = new ArrayList<>(); + torrentDao.readAll().stream() + .map(TorrentEntity::toDomain) + .forEach(torrents::add); + infoHashToTorrent = torrents.stream() + .collect(HashMap::new, (map, torrent) -> map.put(torrent.getInfoHash(), torrent), Map::putAll); + this.torrentsObservable = new MutableRxObservableList<>(torrents); + } + + @Override + public RxObservableList getTorrents() { + return torrentsObservable; + } + + @Override + public Torrent getTorrent(Sha1Hash infoHash) { + return infoHashToTorrent.get(infoHash); + } + + @Override + public void addTorrent(Torrent torrent) { + if (isExistingTorrent(torrent)) { + // TODO: maybe throw exception if torrent already exists? + return; + } + torrentDao.create(TorrentEntity.fromDomain(torrent)); + infoHashToTorrent.put(torrent.getInfoHash(), torrent); + torrentsObservable.add(torrent); + } + + @Override + public void persistTorrents() { + infoHashToTorrent.values().stream() + .map(TorrentEntity::fromDomain) + .forEach(torrentDao::update); + } + + @Override + public void removeTorrent(Torrent torrent) { + torrentDao.delete(torrent.getInfoHash().getBytes()); + infoHashToTorrent.remove(torrent.getInfoHash()); + torrentsObservable.remove(torrent); + } + + private boolean isExistingTorrent(Torrent torrent) { + return infoHashToTorrent.containsKey(torrent.getInfoHash()); + } +} diff --git a/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java b/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java deleted file mode 100644 index 603b6d45..00000000 --- a/src/main/java/jtorrent/data/torrent/repository/FileTorrentRepository.java +++ /dev/null @@ -1,202 +0,0 @@ -package jtorrent.data.torrent.repository; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import jtorrent.data.torrent.source.db.dao.TorrentDao; -import jtorrent.data.torrent.source.db.model.TorrentEntity; -import jtorrent.data.torrent.source.file.model.BencodedFile; -import jtorrent.data.torrent.source.file.model.BencodedInfo; -import jtorrent.data.torrent.source.file.model.BencodedMultiFileInfo; -import jtorrent.data.torrent.source.file.model.BencodedSingleFileInfo; -import jtorrent.data.torrent.source.file.model.BencodedTorrent; -import jtorrent.domain.common.util.ContinuousMergedInputStream; -import jtorrent.domain.common.util.Sha1Hash; -import jtorrent.domain.common.util.rx.MutableRxObservableList; -import jtorrent.domain.common.util.rx.RxObservableList; -import jtorrent.domain.torrent.model.Torrent; -import jtorrent.domain.torrent.model.TorrentMetadata; -import jtorrent.domain.torrent.repository.TorrentRepository; - -public class FileTorrentRepository implements TorrentRepository { - - private final MutableRxObservableList torrentsObservable; - private final Map infoHashToTorrent; - private final TorrentDao torrentDao = new TorrentDao(); - - public FileTorrentRepository() { - List torrents = new ArrayList<>(); - torrentDao.readAll().stream() - .map(TorrentEntity::toDomain) - .forEach(torrents::add); - infoHashToTorrent = torrents.stream() - .collect(HashMap::new, (map, torrent) -> map.put(torrent.getInfoHash(), torrent), Map::putAll); - this.torrentsObservable = new MutableRxObservableList<>(torrents); - } - - @Override - public RxObservableList getTorrents() { - return torrentsObservable; - } - - @Override - public Torrent getTorrent(Sha1Hash infoHash) { - return infoHashToTorrent.get(infoHash); - } - - @Override - public void addTorrent(Torrent torrent) { - if (isExistingTorrent(torrent)) { - // TODO: maybe throw exception if torrent already exists? - return; - } - torrentDao.create(TorrentEntity.fromDomain(torrent)); - infoHashToTorrent.put(torrent.getInfoHash(), torrent); - torrentsObservable.add(torrent); - } - - @Override - public void persistTorrents() { - infoHashToTorrent.values().stream() - .map(TorrentEntity::fromDomain) - .forEach(torrentDao::update); - } - - @Override - public void removeTorrent(Torrent torrent) { - torrentDao.delete(torrent.getInfoHash().getBytes()); - infoHashToTorrent.remove(torrent.getInfoHash()); - torrentsObservable.remove(torrent); - } - - @Override - public TorrentMetadata loadTorrent(File file) throws IOException { - InputStream inputStream = new FileInputStream(file); - return loadTorrent(inputStream); - } - - private TorrentMetadata loadTorrent(InputStream inputStream) throws IOException { - return BencodedTorrent.decode(inputStream).toDomain(); - } - - @Override - public TorrentMetadata loadTorrent(URL url) throws IOException { - // For some reason decoding directly from the URL stream doesn't work, so we have to read it into a byte array - // first. - try (BufferedInputStream in = new BufferedInputStream(url.openStream()); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ) { - byte[] dataBuffer = new byte[1024]; - int bytesRead; - while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { - out.write(dataBuffer, 0, bytesRead); - } - - try (InputStream inputStream = new ByteArrayInputStream(out.toByteArray())) { - return loadTorrent(inputStream); - } - } - } - - @Override - public void saveTorrent(TorrentMetadata torrentMetadata, Path savePath) throws IOException { - BencodedTorrent bencodedTorrent = BencodedTorrent.fromDomain(torrentMetadata); - try (OutputStream outputStream = Files.newOutputStream(savePath)) { - outputStream.write(bencodedTorrent.bencode()); - } - } - - /** - * Create a new {@link TorrentMetadata} instance with the current time as the creation date. - * - * @param trackerUrls list of tiers, each containing a list of tracker URLs - * @param comment comment about the torrent - * @param createdBy name and version of the program used to create the .torrent - * @param pieceSize size of each piece in bytes - * @return a new {@link TorrentMetadata} instance - */ - @Override - public TorrentMetadata createNewTorrent(Path source, List> trackerUrls, String comment, - String createdBy, int pieceSize) throws IOException { - Long creationDate = LocalDateTime.now().toEpochSecond(OffsetDateTime.now().getOffset()); - BencodedInfo info = buildBencodedInfo(source, pieceSize); - return BencodedTorrent.withAnnounceList(creationDate, trackerUrls, comment, createdBy, info).toDomain(); - } - - private static BencodedInfo buildBencodedInfo(Path source, int pieceSize) throws IOException { - if (Files.isDirectory(source)) { - return buildBencodedMultiFileInfo(source, pieceSize); - } else { - return buildBencondedSingleFileInfo(source, pieceSize); - } - } - - private static BencodedSingleFileInfo buildBencondedSingleFileInfo(Path source, int pieceSize) throws IOException { - if (Files.isDirectory(source)) { - throw new IllegalArgumentException("Source must be a file"); - } - - byte[] hashes = computeHashes(Files.newInputStream(source), pieceSize); - String fileName = source.getFileName().toString(); - long length = Files.size(source); - return new BencodedSingleFileInfo(pieceSize, hashes, fileName, length); - } - - private static BencodedMultiFileInfo buildBencodedMultiFileInfo(Path source, int pieceSize) throws IOException { - if (!Files.isDirectory(source)) { - throw new IllegalArgumentException("Source must be a directory"); - } - - List filePaths = getFilesInDirectory(source); - - List inputStreams = new ArrayList<>(); - List files = new ArrayList<>(); - for (Path filePath : filePaths) { - long length = Files.size(filePath); - files.add(BencodedFile.fromPath(source.relativize(filePath), length)); - inputStreams.add(Files.newInputStream(filePath)); - } - - byte[] hashes = computeHashes(new ContinuousMergedInputStream(inputStreams), pieceSize); - String dirName = source.getFileName().toString(); - return new BencodedMultiFileInfo(pieceSize, hashes, dirName, files); - } - - private static List getFilesInDirectory(Path directory) throws IOException { - try (Stream stream = Files.walk(directory)) { - return stream.filter(Files::isRegularFile).toList(); - } - } - - private static byte[] computeHashes(InputStream inputStream, int pieceSize) throws IOException { - List hashes = new ArrayList<>(); - int bytesRead; - byte[] buffer = new byte[pieceSize]; - while ((bytesRead = inputStream.read(buffer)) != -1) { - byte[] piece = Arrays.copyOf(buffer, bytesRead); - hashes.add(Sha1Hash.of(piece)); - } - return Sha1Hash.concatHashes(hashes); - } - - private boolean isExistingTorrent(Torrent torrent) { - return infoHashToTorrent.containsKey(torrent.getInfoHash()); - } -} diff --git a/src/main/java/jtorrent/data/torrent/source/file/filemanager/BencodedTorrentFileManager.java b/src/main/java/jtorrent/data/torrent/source/file/filemanager/BencodedTorrentFileManager.java new file mode 100644 index 00000000..c65d2407 --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/file/filemanager/BencodedTorrentFileManager.java @@ -0,0 +1,68 @@ +package jtorrent.data.torrent.source.file.filemanager; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +import jtorrent.data.torrent.source.file.model.BencodedTorrent; + +public class BencodedTorrentFileManager { + + /** + * Reads a torrent file from the given URL. + * + * @param url the URL where the torrent file is located + * @return the bencoded torrent read from the file + * @throws IOException if an error occurs while reading the file + */ + public BencodedTorrent read(URL url) throws IOException { + // For some reason decoding directly from the URL stream doesn't work, so we have to read it into a byte array + // first. + try (BufferedInputStream in = new BufferedInputStream(url.openStream()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ) { + byte[] dataBuffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { + out.write(dataBuffer, 0, bytesRead); + } + + try (InputStream inputStream = new ByteArrayInputStream(out.toByteArray())) { + return BencodedTorrent.decode(inputStream); + } + } + } + + /** + * Reads a torrent file from the given file. + * + * @param file the file to read the torrent from + * @return the bencoded torrent read from the file + * @throws IOException if an error occurs while reading the file + */ + public BencodedTorrent read(File file) throws IOException { + InputStream inputStream = new FileInputStream(file); + return BencodedTorrent.decode(inputStream); + } + + /** + * Creates a new torrent file at the given path with the given bencoded torrent. + * + * @param path the path to create the torrent file at + * @param bencodedTorrent the bencoded torrent to write + * @throws IOException if an error occurs while writing the file + */ + public void write(Path path, BencodedTorrent bencodedTorrent) throws IOException { + try (OutputStream outputStream = Files.newOutputStream(path)) { + outputStream.write(bencodedTorrent.bencode()); + } + } +} diff --git a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrentFactory.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrentFactory.java new file mode 100644 index 00000000..fa5541f1 --- /dev/null +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrentFactory.java @@ -0,0 +1,84 @@ +package jtorrent.data.torrent.source.file.model; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import jtorrent.domain.common.util.ContinuousMergedInputStream; +import jtorrent.domain.common.util.Sha1Hash; + +public class BencodedTorrentFactory { + + private BencodedTorrentFactory() { + } + + public static BencodedTorrent create(Path source, List> trackerUrls, String comment, + String createdBy, int pieceSize) throws IOException { + Long creationDate = LocalDateTime.now().toEpochSecond(OffsetDateTime.now().getOffset()); + BencodedInfo info = buildBencodedInfo(source, pieceSize); + return BencodedTorrent.withAnnounceList(creationDate, trackerUrls, comment, createdBy, info); + } + + private static BencodedInfo buildBencodedInfo(Path source, int pieceSize) throws IOException { + if (Files.isDirectory(source)) { + return buildBencodedMultiFileInfo(source, pieceSize); + } else { + return buildBencondedSingleFileInfo(source, pieceSize); + } + } + + private static BencodedSingleFileInfo buildBencondedSingleFileInfo(Path source, int pieceSize) throws IOException { + if (Files.isDirectory(source)) { + throw new IllegalArgumentException("Source must be a file"); + } + + byte[] hashes = computeHashes(Files.newInputStream(source), pieceSize); + String fileName = source.getFileName().toString(); + long length = Files.size(source); + return new BencodedSingleFileInfo(pieceSize, hashes, fileName, length); + } + + private static BencodedMultiFileInfo buildBencodedMultiFileInfo(Path source, int pieceSize) throws IOException { + if (!Files.isDirectory(source)) { + throw new IllegalArgumentException("Source must be a directory"); + } + + List filePaths = getFilesInDirectory(source); + + List inputStreams = new ArrayList<>(); + List files = new ArrayList<>(); + for (Path filePath : filePaths) { + long length = Files.size(filePath); + files.add(BencodedFile.fromPath(source.relativize(filePath), length)); + inputStreams.add(Files.newInputStream(filePath)); + } + + byte[] hashes = computeHashes(new ContinuousMergedInputStream(inputStreams), pieceSize); + String dirName = source.getFileName().toString(); + return new BencodedMultiFileInfo(pieceSize, hashes, dirName, files); + } + + private static List getFilesInDirectory(Path directory) throws IOException { + try (Stream stream = Files.walk(directory)) { + return stream.filter(Files::isRegularFile).toList(); + } + } + + private static byte[] computeHashes(InputStream inputStream, int pieceSize) throws IOException { + List hashes = new ArrayList<>(); + int bytesRead; + byte[] buffer = new byte[pieceSize]; + while ((bytesRead = inputStream.read(buffer)) != -1) { + byte[] piece = Arrays.copyOf(buffer, bytesRead); + hashes.add(Sha1Hash.of(piece)); + } + return Sha1Hash.concatHashes(hashes); + } +} diff --git a/src/main/java/jtorrent/domain/Client.java b/src/main/java/jtorrent/domain/Client.java index 12b2eec7..50825553 100644 --- a/src/main/java/jtorrent/domain/Client.java +++ b/src/main/java/jtorrent/domain/Client.java @@ -29,6 +29,7 @@ import jtorrent.domain.torrent.model.Torrent; import jtorrent.domain.torrent.model.TorrentMetadata; import jtorrent.domain.torrent.repository.PieceRepository; +import jtorrent.domain.torrent.repository.TorrentMetadataRepository; import jtorrent.domain.torrent.repository.TorrentRepository; public class Client implements LocalServiceDiscoveryManager.Listener, TorrentHandler.Listener, @@ -41,13 +42,15 @@ public class Client implements LocalServiceDiscoveryManager.Listener, TorrentHan private final DhtClient dhtManager; private final Map infoHashToTorrentHandler = new HashMap<>(); private final TorrentRepository torrentRepository; + private final TorrentMetadataRepository torrentMetadataRepository; private final PieceRepository pieceRepository; private final HandleInboundConnectionsTask handleInboundConnectionsTask = new HandleInboundConnectionsTask(); - public Client(TorrentRepository torrentRepository, PieceRepository pieceRepository, - InboundConnectionListener inboundConnectionListener, + public Client(TorrentRepository torrentRepository, TorrentMetadataRepository torrentMetadataRepository, + PieceRepository pieceRepository, InboundConnectionListener inboundConnectionListener, LocalServiceDiscoveryManager localServiceDiscoveryManager, DhtClient dhtClient) { this.torrentRepository = torrentRepository; + this.torrentMetadataRepository = torrentMetadataRepository; this.pieceRepository = pieceRepository; this.inboundConnectionListener = inboundConnectionListener; @@ -97,12 +100,12 @@ public void removeTorrent(Torrent torrent) { torrentRepository.removeTorrent(torrent); } - public TorrentMetadata loadTorrent(File file) throws IOException { - return torrentRepository.loadTorrent(file); + public TorrentMetadata loadTorrentMetadata(File file) throws IOException { + return torrentMetadataRepository.getTorrentMetadata(file); } - public TorrentMetadata loadTorrent(URL url) throws IOException { - return torrentRepository.loadTorrent(url); + public TorrentMetadata loadTorrentMetadata(URL url) throws IOException { + return torrentMetadataRepository.getTorrentMetadata(url); } public void startTorrent(Torrent torrent) { @@ -180,9 +183,9 @@ public double getUploadRate() { public void createNewTorrent(Path savePath, Path source, List> trackerUrls, String comment, int pieceSize) throws IOException { - TorrentMetadata torrentMetadata = torrentRepository.createNewTorrent(source, trackerUrls, comment, + TorrentMetadata torrentMetadata = torrentMetadataRepository.createNewTorrent(source, trackerUrls, comment, "JTorrent", pieceSize); - torrentRepository.saveTorrent(torrentMetadata, savePath); + torrentMetadataRepository.saveTorrentMetadata(torrentMetadata, savePath); } public Torrent getTorrent(Sha1Hash infoHash) { diff --git a/src/main/java/jtorrent/domain/torrent/repository/TorrentMetadataRepository.java b/src/main/java/jtorrent/domain/torrent/repository/TorrentMetadataRepository.java new file mode 100644 index 00000000..fc9bb05e --- /dev/null +++ b/src/main/java/jtorrent/domain/torrent/repository/TorrentMetadataRepository.java @@ -0,0 +1,21 @@ +package jtorrent.domain.torrent.repository; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Path; +import java.util.List; + +import jtorrent.domain.torrent.model.TorrentMetadata; + +public interface TorrentMetadataRepository { + + TorrentMetadata getTorrentMetadata(File file) throws IOException; + + TorrentMetadata getTorrentMetadata(URL url) throws IOException; + + void saveTorrentMetadata(TorrentMetadata torrentMetadata, Path savePath) throws IOException; + + TorrentMetadata createNewTorrent(Path source, List> trackerUrls, String comment, String createdBy, + int pieceSize) throws IOException; +} diff --git a/src/main/java/jtorrent/domain/torrent/repository/TorrentRepository.java b/src/main/java/jtorrent/domain/torrent/repository/TorrentRepository.java index 4be4e1b9..35145555 100644 --- a/src/main/java/jtorrent/domain/torrent/repository/TorrentRepository.java +++ b/src/main/java/jtorrent/domain/torrent/repository/TorrentRepository.java @@ -1,15 +1,8 @@ package jtorrent.domain.torrent.repository; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Path; -import java.util.List; - import jtorrent.domain.common.util.Sha1Hash; import jtorrent.domain.common.util.rx.RxObservableList; import jtorrent.domain.torrent.model.Torrent; -import jtorrent.domain.torrent.model.TorrentMetadata; public interface TorrentRepository { @@ -22,13 +15,4 @@ public interface TorrentRepository { void persistTorrents(); void removeTorrent(Torrent torrent); - - TorrentMetadata loadTorrent(File file) throws IOException; - - TorrentMetadata loadTorrent(URL url) throws IOException; - - void saveTorrent(TorrentMetadata torrentMetadata, Path savePath) throws IOException; - - TorrentMetadata createNewTorrent(Path source, List> trackerUrls, String comment, String createdBy, - int pieceSize) throws IOException; } diff --git a/src/main/java/jtorrent/presentation/JTorrent.java b/src/main/java/jtorrent/presentation/JTorrent.java index 25371b3f..6819e692 100644 --- a/src/main/java/jtorrent/presentation/JTorrent.java +++ b/src/main/java/jtorrent/presentation/JTorrent.java @@ -7,14 +7,16 @@ import javafx.application.Application; import javafx.stage.Stage; -import jtorrent.data.torrent.repository.FilePieceRepository; -import jtorrent.data.torrent.repository.FileTorrentRepository; +import jtorrent.data.torrent.repository.AppPieceRepository; +import jtorrent.data.torrent.repository.AppTorrentMetadataRepository; +import jtorrent.data.torrent.repository.AppTorrentRepository; import jtorrent.domain.Client; import jtorrent.domain.common.Constants; import jtorrent.domain.dht.DhtClient; import jtorrent.domain.inbound.InboundConnectionListener; import jtorrent.domain.lsd.LocalServiceDiscoveryManager; import jtorrent.domain.torrent.repository.PieceRepository; +import jtorrent.domain.torrent.repository.TorrentMetadataRepository; import jtorrent.domain.torrent.repository.TorrentRepository; import jtorrent.presentation.main.viewmodel.MainViewModel; @@ -32,10 +34,11 @@ public void init() throws Exception { DhtClient dhtClient = new DhtClient(Constants.PORT); - TorrentRepository repository = new FileTorrentRepository(); - PieceRepository pieceRepository = new FilePieceRepository(); - client = new Client(repository, pieceRepository, inboundConnectionListener, new LocalServiceDiscoveryManager(), - dhtClient); + TorrentRepository torrentRepository = new AppTorrentRepository(); + TorrentMetadataRepository torrentMetadataRepository = new AppTorrentMetadataRepository(); + PieceRepository pieceRepository = new AppPieceRepository(); + client = new Client(torrentRepository, torrentMetadataRepository, pieceRepository, inboundConnectionListener, + new LocalServiceDiscoveryManager(), dhtClient); } @Override diff --git a/src/main/java/jtorrent/presentation/main/viewmodel/MainViewModel.java b/src/main/java/jtorrent/presentation/main/viewmodel/MainViewModel.java index 749901e5..1646aa7d 100644 --- a/src/main/java/jtorrent/presentation/main/viewmodel/MainViewModel.java +++ b/src/main/java/jtorrent/presentation/main/viewmodel/MainViewModel.java @@ -64,12 +64,12 @@ public ChartViewModel getChartViewModel() { } public TorrentMetadata loadTorrent(File file) throws IOException { - return client.loadTorrent(file); + return client.loadTorrentMetadata(file); } public TorrentMetadata loadTorrent(String urlString) throws IOException { URL url = new URL(urlString); - return client.loadTorrent(url); + return client.loadTorrentMetadata(url); } public void addTorrent(TorrentMetadata torrentMetadata, AddNewTorrentDialog.Result result) { diff --git a/src/main/java/jtorrent/presentation/main/viewmodel/TorrentControlsViewModel.java b/src/main/java/jtorrent/presentation/main/viewmodel/TorrentControlsViewModel.java index e195605f..50a88ddc 100644 --- a/src/main/java/jtorrent/presentation/main/viewmodel/TorrentControlsViewModel.java +++ b/src/main/java/jtorrent/presentation/main/viewmodel/TorrentControlsViewModel.java @@ -75,11 +75,11 @@ public void removeSelectedTorrent() { public TorrentMetadata loadTorrentContents(String urlString) throws IOException { URL url = new URL(urlString); - return client.loadTorrent(url); + return client.loadTorrentMetadata(url); } public TorrentMetadata loadTorrentContents(File file) throws IOException { - return client.loadTorrent(file); + return client.loadTorrentMetadata(file); } public void addTorrent(TorrentMetadata torrentMetadata, AddNewTorrentDialog.Result result) { From f8ecdd50ca29a1e4e8efd3552e2aa6ad6168d6cf Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 26 May 2024 02:27:35 +0800 Subject: [PATCH 17/33] Rename method --- .../data/torrent/repository/AppTorrentMetadataRepository.java | 4 ++-- src/main/java/jtorrent/domain/Client.java | 2 +- .../domain/torrent/repository/TorrentMetadataRepository.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/jtorrent/data/torrent/repository/AppTorrentMetadataRepository.java b/src/main/java/jtorrent/data/torrent/repository/AppTorrentMetadataRepository.java index 212321da..3b9bafa6 100644 --- a/src/main/java/jtorrent/data/torrent/repository/AppTorrentMetadataRepository.java +++ b/src/main/java/jtorrent/data/torrent/repository/AppTorrentMetadataRepository.java @@ -33,7 +33,7 @@ public void saveTorrentMetadata(TorrentMetadata torrentMetadata, Path savePath) } /** - * Create a new {@link TorrentMetadata} instance with the current time as the creation date. + * Creates a new {@link TorrentMetadata} instance with the current time as the creation date. * * @param trackerUrls list of tiers, each containing a list of tracker URLs * @param comment comment about the torrent @@ -42,7 +42,7 @@ public void saveTorrentMetadata(TorrentMetadata torrentMetadata, Path savePath) * @return a new {@link TorrentMetadata} instance */ @Override - public TorrentMetadata createNewTorrent(Path source, List> trackerUrls, String comment, + public TorrentMetadata createTOrrentMetadata(Path source, List> trackerUrls, String comment, String createdBy, int pieceSize) throws IOException { return BencodedTorrentFactory.create(source, trackerUrls, comment, createdBy, pieceSize).toDomain(); } diff --git a/src/main/java/jtorrent/domain/Client.java b/src/main/java/jtorrent/domain/Client.java index 50825553..eef5f127 100644 --- a/src/main/java/jtorrent/domain/Client.java +++ b/src/main/java/jtorrent/domain/Client.java @@ -183,7 +183,7 @@ public double getUploadRate() { public void createNewTorrent(Path savePath, Path source, List> trackerUrls, String comment, int pieceSize) throws IOException { - TorrentMetadata torrentMetadata = torrentMetadataRepository.createNewTorrent(source, trackerUrls, comment, + TorrentMetadata torrentMetadata = torrentMetadataRepository.createTOrrentMetadata(source, trackerUrls, comment, "JTorrent", pieceSize); torrentMetadataRepository.saveTorrentMetadata(torrentMetadata, savePath); } diff --git a/src/main/java/jtorrent/domain/torrent/repository/TorrentMetadataRepository.java b/src/main/java/jtorrent/domain/torrent/repository/TorrentMetadataRepository.java index fc9bb05e..da32569c 100644 --- a/src/main/java/jtorrent/domain/torrent/repository/TorrentMetadataRepository.java +++ b/src/main/java/jtorrent/domain/torrent/repository/TorrentMetadataRepository.java @@ -16,6 +16,6 @@ public interface TorrentMetadataRepository { void saveTorrentMetadata(TorrentMetadata torrentMetadata, Path savePath) throws IOException; - TorrentMetadata createNewTorrent(Path source, List> trackerUrls, String comment, String createdBy, + TorrentMetadata createTOrrentMetadata(Path source, List> trackerUrls, String comment, String createdBy, int pieceSize) throws IOException; } From 55eca3c7f9d89e472cdb0ad6fca5f9d92c154688 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 26 May 2024 02:31:00 +0800 Subject: [PATCH 18/33] Modify TorrentMetadata to model tiers of trackers --- .../db/model/TorrentMetadataComponent.java | 39 +++++++++++++------ .../source/file/model/BencodedTorrent.java | 29 +++++++------- .../domain/torrent/model/Torrent.java | 2 +- .../domain/torrent/model/TorrentMetadata.java | 12 ++++-- .../file/model/BencodedTorrentTest.java | 9 ++--- 5 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java index f1e0f1f5..704b1901 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentMetadataComponent.java @@ -2,22 +2,31 @@ import java.net.URI; import java.time.LocalDateTime; +import java.util.Arrays; import java.util.Collections; -import java.util.Set; +import java.util.List; import java.util.stream.Collectors; import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; import jakarta.persistence.Embeddable; import jakarta.persistence.Embedded; +import jakarta.persistence.OrderColumn; import jtorrent.domain.torrent.model.FileInfo; import jtorrent.domain.torrent.model.TorrentMetadata; @Embeddable public class TorrentMetadataComponent { + private static final String TRACKER_SEPARATOR = " "; + + /** + * The list of tracker tiers. Each tier is a String made up of tracker URLs separated by a space. + * This is a workaround to the fact that JPA does not support nested collections. + */ + @OrderColumn @ElementCollection - private final Set trackers; + private final List trackers; @Column(nullable = false) private final LocalDateTime creationDate; @@ -32,10 +41,10 @@ public class TorrentMetadataComponent { private final FileInfoComponent fileInfo; protected TorrentMetadataComponent() { - this(Collections.emptySet(), LocalDateTime.MIN, "", "", new FileInfoComponent()); + this(Collections.emptyList(), LocalDateTime.MIN, "", "", new FileInfoComponent()); } - public TorrentMetadataComponent(Set trackers, LocalDateTime creationDate, String comment, String createdBy, + public TorrentMetadataComponent(List trackers, LocalDateTime creationDate, String comment, String createdBy, FileInfoComponent fileInfo) { this.trackers = trackers; this.creationDate = creationDate; @@ -45,25 +54,31 @@ public TorrentMetadataComponent(Set trackers, LocalDateTime creationDate } public static TorrentMetadataComponent fromDomain(TorrentMetadata torrentMetadata) { - Set trackers = torrentMetadata.trackers().stream() - .map(URI::toString) - .collect(Collectors.toSet()); + List trackerTiers = torrentMetadata.trackerTiers().stream() + .map(tier -> tier.stream() + .map(URI::toString) + .collect(Collectors.joining(TRACKER_SEPARATOR)) + ) + .toList(); LocalDateTime creationDate = torrentMetadata.creationDate(); String comment = torrentMetadata.comment(); String createdBy = torrentMetadata.createdBy(); FileInfoComponent fileInfo = FileInfoComponent.fromDomain(torrentMetadata.fileInfo()); - return new TorrentMetadataComponent(trackers, creationDate, comment, createdBy, fileInfo); + return new TorrentMetadataComponent(trackerTiers, creationDate, comment, createdBy, fileInfo); } public TorrentMetadata toDomain() { - Set domainTrackers = trackers.stream() - .map(URI::create) - .collect(Collectors.toSet()); + List> domainTrackers = trackers.stream() + .map(tier -> Arrays.stream(tier.split(TRACKER_SEPARATOR)) + .map(URI::create) + .toList() + ) + .toList(); FileInfo domainFileInfo = fileInfo.toDomain(); return new TorrentMetadata(domainTrackers, creationDate, comment, createdBy, domainFileInfo); } - public Set getTrackers() { + public List getTrackers() { return trackers; } diff --git a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrent.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrent.java index 7e85acd2..e5b4ae71 100644 --- a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrent.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrent.java @@ -13,11 +13,9 @@ import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.stream.Collectors; import com.dampcake.bencode.BencodeInputStream; @@ -103,12 +101,13 @@ public static BencodedTorrent fromMap(Map map) { public static BencodedTorrent fromDomain(TorrentMetadata torrentMetadata) { Long creationDate = torrentMetadata.creationDate().toEpochSecond(OffsetDateTime.now().getOffset()); // TODO: proper handling of tracker groups - String announce = torrentMetadata.trackers().iterator().next().toString(); - List> announceList = - List.of(torrentMetadata.trackers().stream() + String announce = torrentMetadata.trackerTiers().get(0).get(0).toString(); + List> announceList = torrentMetadata.trackerTiers().stream() + .map(tier -> tier.stream() .map(URI::toString) .toList() - ); + ) + .toList(); BencodedInfo info = BencodedInfoFactory.fromDomain(torrentMetadata.fileInfo()); return new BencodedTorrent(creationDate, announce, announceList, torrentMetadata.comment(), torrentMetadata.createdBy(), info); @@ -152,14 +151,16 @@ public Collection getFiles() { public TorrentMetadata toDomain() { try { - Set trackers = new HashSet<>(); - trackers.add(URI.create(announce)); - - if (announceList != null) { - announceList.stream() - .flatMap(List::stream) - .map(URI::create) - .collect(Collectors.toCollection(() -> trackers)); + final List> trackers; + + if (announceList != null && !announceList.isEmpty()) { + trackers = announceList.stream() + .map(tier -> tier.stream() + .map(URI::create) + .toList() + ).toList(); + } else { + trackers = List.of(List.of(URI.create(announce))); } LocalDateTime creationDateTime = LocalDateTime.ofEpochSecond(creationDate, 0, diff --git a/src/main/java/jtorrent/domain/torrent/model/Torrent.java b/src/main/java/jtorrent/domain/torrent/model/Torrent.java index a3e07155..c9f593a1 100644 --- a/src/main/java/jtorrent/domain/torrent/model/Torrent.java +++ b/src/main/java/jtorrent/domain/torrent/model/Torrent.java @@ -49,7 +49,7 @@ public Torrent(TorrentMetadata torrentMetaData, TorrentStatistics torrentStatist this.state = requireNonNull(state); this.stateSubject = BehaviorSubject.createDefault(state); - torrentMetaData.trackers().stream() + torrentMetaData.trackerTiers().get(0).stream() .map(TrackerFactory::fromUri) .collect(Collectors.toCollection(() -> trackers)); } diff --git a/src/main/java/jtorrent/domain/torrent/model/TorrentMetadata.java b/src/main/java/jtorrent/domain/torrent/model/TorrentMetadata.java index fa95cce1..e5581036 100644 --- a/src/main/java/jtorrent/domain/torrent/model/TorrentMetadata.java +++ b/src/main/java/jtorrent/domain/torrent/model/TorrentMetadata.java @@ -2,8 +2,14 @@ import java.net.URI; import java.time.LocalDateTime; -import java.util.Set; +import java.util.List; -public record TorrentMetadata(Set trackers, LocalDateTime creationDate, String comment, String createdBy, - FileInfo fileInfo) { +public record TorrentMetadata(List> trackerTiers, LocalDateTime creationDate, String comment, + String createdBy, FileInfo fileInfo) { + + public TorrentMetadata { + if (trackerTiers.isEmpty() || trackerTiers.get(0).isEmpty()) { + throw new IllegalArgumentException("At least one tracker is required"); + } + } } diff --git a/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java b/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java index 9f2571b4..083c8cd4 100644 --- a/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java +++ b/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java @@ -16,7 +16,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import org.junit.jupiter.api.Test; @@ -239,7 +238,7 @@ void toDomain_singleFile() throws NoSuchAlgorithmException, IOException { TorrentMetadata actual = bencodedTorrent.toDomain(); TorrentMetadata expected = new TorrentMetadataBuilder() - .setTrackers(Set.of(URI.create("udp://tracker.example.com:80/announce"))) + .setTrackers(List.of(List.of(URI.create("udp://tracker.example.com:80/announce")))) .setCreationDate(LocalDateTime.ofEpochSecond(123456789L, 0, OffsetDateTime.now().getOffset())) .setComment("comment") .setCreatedBy("created by") @@ -292,7 +291,7 @@ void toDomain_multiFile() throws NoSuchAlgorithmException, IOException { TorrentMetadata actual = bencodedTorrent.toDomain(); TorrentMetadata expected = new TorrentMetadataBuilder() - .setTrackers(Set.of(URI.create("udp://tracker.example.com:80/announce"))) + .setTrackers(List.of(List.of(URI.create("udp://tracker.example.com:80/announce")))) .setCreationDate(LocalDateTime.ofEpochSecond(123456789L, 0, OffsetDateTime.now().getOffset())) .setComment("comment") .setCreatedBy("created by") @@ -458,7 +457,7 @@ public BencodedFile build() { private static class TorrentMetadataBuilder { - private Set trackers = Collections.emptySet(); + private List> trackers = Collections.emptyList(); private LocalDateTime creationDate = LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC); private String comment = ""; private String createdBy = ""; @@ -469,7 +468,7 @@ private static class TorrentMetadataBuilder { private List fileMetadata = Collections.emptyList(); private Sha1Hash infoHash = new Sha1Hash(new byte[20]); - public TorrentMetadataBuilder setTrackers(Set trackers) { + public TorrentMetadataBuilder setTrackers(List> trackers) { this.trackers = trackers; return this; } From 8a7c3f5a7d21b46621d6ef08a28c1e96b3770b1c Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 26 May 2024 02:31:52 +0800 Subject: [PATCH 19/33] Fix typo --- .../torrent/source/file/model/BencodedTorrentFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrentFactory.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrentFactory.java index fa5541f1..e96be872 100644 --- a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrentFactory.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedTorrentFactory.java @@ -30,11 +30,11 @@ private static BencodedInfo buildBencodedInfo(Path source, int pieceSize) throws if (Files.isDirectory(source)) { return buildBencodedMultiFileInfo(source, pieceSize); } else { - return buildBencondedSingleFileInfo(source, pieceSize); + return buildBencodedSingleFileInfo(source, pieceSize); } } - private static BencodedSingleFileInfo buildBencondedSingleFileInfo(Path source, int pieceSize) throws IOException { + private static BencodedSingleFileInfo buildBencodedSingleFileInfo(Path source, int pieceSize) throws IOException { if (Files.isDirectory(source)) { throw new IllegalArgumentException("Source must be a file"); } From a38b69f7ea15f4b0d631e9a4bd016df8cdea2349 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 26 May 2024 04:24:29 +0800 Subject: [PATCH 20/33] Remove FileMetadata end member since it is derivable --- .../source/db/model/FileMetadataComponent.java | 3 +-- .../source/file/model/BencodedMultiFileInfo.java | 2 +- .../source/file/model/BencodedSingleFileInfo.java | 3 +-- .../jtorrent/domain/torrent/model/FileMetadata.java | 6 +++++- .../source/file/model/BencodedTorrentTest.java | 11 +---------- 5 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java index f1c221be..cb5d4fb5 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java @@ -65,8 +65,7 @@ public FileMetadata toDomain() { firstPieceStart, lastPiece, lastPieceEnd, - start, - start + size - 1 + start ); } diff --git a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java index a786149c..365ac440 100644 --- a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java @@ -95,7 +95,7 @@ protected List buildFileMetaData() { Path filePath = Path.of(String.join("/", sanitizePath(file.getPath()))); FileMetadata fileMetadataItem = new FileMetadata(fileSize, filePath, firstPiece, - firstPieceStart, lastPiece, lastPieceEnd, fileStart, fileEnd); + firstPieceStart, lastPiece, lastPieceEnd, fileStart); fileMetaData.add(fileMetadataItem); } diff --git a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java index 8666f1d1..d32bc6a8 100644 --- a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java @@ -60,8 +60,7 @@ private FileMetadata buildFileMetaData() { long fileEnd = length - 1; int lastPieceEnd = (int) (fileEnd % pieceLength); - return new FileMetadata(length, filePath, 0, 0, - lastPiece, lastPieceEnd, 0, fileEnd); + return new FileMetadata(length, filePath, 0, 0, lastPiece, lastPieceEnd, 0); } @Override diff --git a/src/main/java/jtorrent/domain/torrent/model/FileMetadata.java b/src/main/java/jtorrent/domain/torrent/model/FileMetadata.java index 0d563a3b..91da8bba 100644 --- a/src/main/java/jtorrent/domain/torrent/model/FileMetadata.java +++ b/src/main/java/jtorrent/domain/torrent/model/FileMetadata.java @@ -3,9 +3,13 @@ import java.nio.file.Path; public record FileMetadata(long size, Path path, int firstPiece, int firstPieceStart, int lastPiece, int lastPieceEnd, - long start, long end) { + long start) { public int numPieces() { return lastPiece - firstPiece + 1; } + + public long end() { + return start + size - 1; + } } diff --git a/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java b/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java index 083c8cd4..645f9c90 100644 --- a/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java +++ b/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java @@ -254,7 +254,6 @@ void toDomain_singleFile() throws NoSuchAlgorithmException, IOException { .setLastPiece(0) .setLastPieceEnd(99) .setStart(0) - .setEnd(99) .build() )) .setInfoHash(new Sha1Hash(info.getInfoHash())) @@ -308,7 +307,6 @@ void toDomain_multiFile() throws NoSuchAlgorithmException, IOException { .setLastPiece(0) .setLastPieceEnd(99) .setStart(0) - .setEnd(99) .build(), new FileMetadataBuilder() .setLength(200) @@ -318,7 +316,6 @@ void toDomain_multiFile() throws NoSuchAlgorithmException, IOException { .setLastPiece(2) .setLastPieceEnd(99) .setStart(100) - .setEnd(299) .build() )) .setInfoHash(new Sha1Hash(info.getInfoHash())) @@ -542,7 +539,6 @@ private static class FileMetadataBuilder { private int lastPiece; private int lastPieceEnd; private long start; - private long end; public FileMetadataBuilder setLength(int length) { this.length = length; @@ -579,13 +575,8 @@ public FileMetadataBuilder setStart(long start) { return this; } - public FileMetadataBuilder setEnd(long end) { - this.end = end; - return this; - } - public FileMetadata build() { - return new FileMetadata(length, path, firstPiece, firstPieceStart, lastPiece, lastPieceEnd, start, end); + return new FileMetadata(length, path, firstPiece, firstPieceStart, lastPiece, lastPieceEnd, start); } } } From d7df6c8babcf8f8400b68ee05d13f1a14a14d11d Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 26 May 2024 07:00:10 +0800 Subject: [PATCH 21/33] Change FileMetadata constructor argument order --- .../data/torrent/source/db/model/FileMetadataComponent.java | 6 +++--- .../torrent/source/file/model/BencodedMultiFileInfo.java | 4 ++-- .../torrent/source/file/model/BencodedSingleFileInfo.java | 2 +- .../java/jtorrent/domain/torrent/model/FileMetadata.java | 4 ++-- .../data/torrent/source/file/model/BencodedTorrentTest.java | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java index cb5d4fb5..446d384f 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/FileMetadataComponent.java @@ -59,13 +59,13 @@ public static FileMetadataComponent fromDomain(FileMetadata fileMetadata) { public FileMetadata toDomain() { return new FileMetadata( - size, Path.of(path), + start, + size, firstPiece, firstPieceStart, lastPiece, - lastPieceEnd, - start + lastPieceEnd ); } diff --git a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java index 365ac440..c381946a 100644 --- a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedMultiFileInfo.java @@ -94,8 +94,8 @@ protected List buildFileMetaData() { prevLastPieceEnd = lastPieceEnd; Path filePath = Path.of(String.join("/", sanitizePath(file.getPath()))); - FileMetadata fileMetadataItem = new FileMetadata(fileSize, filePath, firstPiece, - firstPieceStart, lastPiece, lastPieceEnd, fileStart); + FileMetadata fileMetadataItem = new FileMetadata(filePath, fileStart, fileSize, firstPiece, + firstPieceStart, lastPiece, lastPieceEnd); fileMetaData.add(fileMetadataItem); } diff --git a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java index d32bc6a8..5fed63f9 100644 --- a/src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java +++ b/src/main/java/jtorrent/data/torrent/source/file/model/BencodedSingleFileInfo.java @@ -60,7 +60,7 @@ private FileMetadata buildFileMetaData() { long fileEnd = length - 1; int lastPieceEnd = (int) (fileEnd % pieceLength); - return new FileMetadata(length, filePath, 0, 0, lastPiece, lastPieceEnd, 0); + return new FileMetadata(filePath, 0, length, 0, 0, lastPiece, lastPieceEnd); } @Override diff --git a/src/main/java/jtorrent/domain/torrent/model/FileMetadata.java b/src/main/java/jtorrent/domain/torrent/model/FileMetadata.java index 91da8bba..eb3fb87e 100644 --- a/src/main/java/jtorrent/domain/torrent/model/FileMetadata.java +++ b/src/main/java/jtorrent/domain/torrent/model/FileMetadata.java @@ -2,8 +2,8 @@ import java.nio.file.Path; -public record FileMetadata(long size, Path path, int firstPiece, int firstPieceStart, int lastPiece, int lastPieceEnd, - long start) { +public record FileMetadata(Path path, long start, long size, int firstPiece, int firstPieceStart, int lastPiece, + int lastPieceEnd) { public int numPieces() { return lastPiece - firstPiece + 1; diff --git a/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java b/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java index 645f9c90..a5b41b2a 100644 --- a/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java +++ b/src/test/java/jtorrent/data/torrent/source/file/model/BencodedTorrentTest.java @@ -576,7 +576,7 @@ public FileMetadataBuilder setStart(long start) { } public FileMetadata build() { - return new FileMetadata(length, path, firstPiece, firstPieceStart, lastPiece, lastPieceEnd, start); + return new FileMetadata(path, start, length, firstPiece, firstPieceStart, lastPiece, lastPieceEnd); } } } From 7954716ec46330890d7095d1037c7e2bdae4f1b8 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 26 May 2024 23:37:30 +0800 Subject: [PATCH 22/33] Fix bug in FileProgress.createExisting --- .../java/jtorrent/domain/torrent/model/FileProgress.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/jtorrent/domain/torrent/model/FileProgress.java b/src/main/java/jtorrent/domain/torrent/model/FileProgress.java index c555d061..0cabd627 100644 --- a/src/main/java/jtorrent/domain/torrent/model/FileProgress.java +++ b/src/main/java/jtorrent/domain/torrent/model/FileProgress.java @@ -43,7 +43,11 @@ public static FileProgress createExisting(FileInfo fileInfo, FileMetadata fileMe .filter(verifiedPieces::get) .mapToLong(piece -> getPieceBytesInFile(fileInfo, fileMetaData, piece)) .sum(); - return new FileProgress(fileInfo, fileMetaData, verifiedBytes, verifiedPieces); + BitSet relativeVerifiedPieces = new BitSet(); + IntStream.range(fileMetaData.firstPiece(), fileMetaData.lastPiece() + 1) + .filter(verifiedPieces::get) + .forEach(piece -> relativeVerifiedPieces.set(piece - fileMetaData.firstPiece())); + return new FileProgress(fileInfo, fileMetaData, verifiedBytes, relativeVerifiedPieces); } private static long getPieceBytesInFile(FileInfo fileInfo, FileMetadata fileMetaData, int piece) { From b5e8f33213d279d88c1761dc85a8aaa3207f196e Mon Sep 17 00:00:00 2001 From: Ashuh Date: Mon, 27 May 2024 01:40:41 +0800 Subject: [PATCH 23/33] Fix verifiedPieces column length exceeded --- .../data/torrent/source/db/model/TorrentProgressComponent.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java index 681ab6ed..55c11ece 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java @@ -12,6 +12,7 @@ import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; import jakarta.persistence.Embeddable; +import jakarta.persistence.Lob; import jtorrent.domain.torrent.model.FileInfo; import jtorrent.domain.torrent.model.FileMetadata; import jtorrent.domain.torrent.model.FileProgress; @@ -24,6 +25,7 @@ public class TorrentProgressComponent { @CollectionTable private final Map pieceToReceivedBlocks; + @Lob @Column(nullable = false) private final byte[] verifiedPieces; From 037962c6a9be9efd021f1323a9068229248bd6da Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 2 Jun 2024 04:45:36 +0800 Subject: [PATCH 24/33] Store piece hashes in db as concatenated byte array instead of list of byte arrays --- .../source/db/model/FileInfoComponent.java | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java index f180ece0..c256093e 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java @@ -4,14 +4,13 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.stream.IntStream; import org.hibernate.annotations.Formula; import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; import jakarta.persistence.Embeddable; -import jakarta.persistence.FetchType; +import jakarta.persistence.Lob; import jakarta.persistence.OrderColumn; import jtorrent.domain.common.util.Sha1Hash; import jtorrent.domain.torrent.model.FileInfo; @@ -29,9 +28,8 @@ public class FileInfoComponent { @ElementCollection private final List fileMetadata; - @OrderColumn - @ElementCollection(fetch = FetchType.EAGER) - private final List pieceHashes; + @Lob + private final byte[] pieceHashes; @Column(nullable = false) private final int pieceSize; @@ -40,10 +38,10 @@ public class FileInfoComponent { private final byte[] infoHash; protected FileInfoComponent() { - this(null, Collections.emptyList(), Collections.emptyList(), 0, null); + this(null, Collections.emptyList(), new byte[0], 0, null); } - public FileInfoComponent(String directory, List fileMetadata, List pieceHashes, + public FileInfoComponent(String directory, List fileMetadata, byte[] pieceHashes, int pieceSize, byte[] infoHash) { this.directory = directory; this.fileMetadata = fileMetadata; @@ -64,9 +62,8 @@ public static FileInfoComponent fromDomain(SingleFileInfo singleFileInfo) { List fileMetadata = singleFileInfo.getFileMetaData().stream() .map(FileMetadataComponent::fromDomain) .toList(); - List pieceHashes = singleFileInfo.getPieceHashes().stream() - .map(Sha1Hash::getBytes) - .toList(); + byte[] pieceHashes = Sha1Hash.concatHashes(singleFileInfo.getPieceHashes()); + Sha1Hash.concatHashes(singleFileInfo.getPieceHashes()); int pieceSize = singleFileInfo.getPieceSize(); byte[] infoHash = singleFileInfo.getInfoHash().getBytes(); return new FileInfoComponent(null, fileMetadata, pieceHashes, pieceSize, infoHash); @@ -77,12 +74,10 @@ public static FileInfoComponent fromDomain(MultiFileInfo multiFileInfo) { List fileMetadata = multiFileInfo.getFileMetaData().stream() .map(FileMetadataComponent::fromDomain) .toList(); - List pieceHashes = multiFileInfo.getPieceHashes().stream() - .map(Sha1Hash::getBytes) - .toList(); + byte[] pieceHashes = Sha1Hash.concatHashes(multiFileInfo.getPieceHashes()); int pieceSize = multiFileInfo.getPieceSize(); byte[] infoHash = multiFileInfo.getInfoHash().getBytes(); - return new FileInfoComponent(directory, fileMetadata, pieceHashes, pieceSize, infoHash); + return new FileInfoComponent(directory, List.of(), pieceHashes, pieceSize, infoHash); } public FileInfo toDomain() { @@ -95,9 +90,7 @@ private boolean isSingleFile() { private SingleFileInfo toSingleFileInfo() { FileMetadata domainFileMetadata = fileMetadata.get(0).toDomain(); - List domainPieceHashes = pieceHashes.stream() - .map(Sha1Hash::new) - .toList(); + List domainPieceHashes = Sha1Hash.splitHashes(pieceHashes); Sha1Hash domainInfoHash = new Sha1Hash(infoHash); return new SingleFileInfo(domainFileMetadata, pieceSize, domainPieceHashes, domainInfoHash); } @@ -106,9 +99,7 @@ private MultiFileInfo toMultiFileInfo() { List domainFileMetadata = fileMetadata.stream() .map(FileMetadataComponent::toDomain) .toList(); - List domainPieceHashes = pieceHashes.stream() - .map(Sha1Hash::new) - .toList(); + List domainPieceHashes = Sha1Hash.splitHashes(pieceHashes); Sha1Hash domainInfoHash = new Sha1Hash(infoHash); return new MultiFileInfo(directory, domainFileMetadata, pieceSize, domainPieceHashes, domainInfoHash); } @@ -121,7 +112,7 @@ public List getFileMetadata() { return fileMetadata; } - public List getPieceHashes() { + public byte[] getPieceHashes() { return pieceHashes; } @@ -137,7 +128,7 @@ public byte[] getInfoHash() { public int hashCode() { int result = Objects.hashCode(directory); result = 31 * result + fileMetadata.hashCode(); - result = 31 * result + pieceHashes.hashCode(); + result = 31 * result + Arrays.hashCode(pieceHashes); result = 31 * result + pieceSize; result = 31 * result + Arrays.hashCode(infoHash); return result; @@ -156,9 +147,7 @@ public boolean equals(Object o) { return pieceSize == that.pieceSize && Objects.equals(directory, that.directory) && fileMetadata.equals(that.fileMetadata) - && pieceHashes.size() == that.pieceHashes.size() - && IntStream.range(0, pieceHashes.size()) - .allMatch(i -> Arrays.equals(pieceHashes.get(i), that.pieceHashes.get(i))) + && Arrays.equals(pieceHashes, that.pieceHashes) && Arrays.equals(infoHash, that.infoHash); } @@ -167,7 +156,7 @@ public String toString() { return "FileInfoComponent{" + "directory='" + directory + '\'' + ", fileMetadata=" + fileMetadata - + ", pieceHashes=" + pieceHashes.stream().map(Arrays::toString).toList() + + ", pieceHashes=" + Arrays.toString(pieceHashes) + ", pieceSize=" + pieceSize + ", infoHash=" + Arrays.toString(infoHash) + '}'; From b7b50c8ddd9fc1f0b6a9d88ce9a5ade0b8ac0940 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 2 Jun 2024 17:15:25 +0800 Subject: [PATCH 25/33] Store piece to received blocks map as serialized bytes in db --- .../db/model/TorrentProgressComponent.java | 75 ++++++++++++++----- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java index 55c11ece..c9d6c94d 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponent.java @@ -1,16 +1,19 @@ package jtorrent.data.torrent.source.db.model; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; import java.util.BitSet; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; -import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; import jakarta.persistence.Embeddable; import jakarta.persistence.Lob; import jtorrent.domain.torrent.model.FileInfo; @@ -21,29 +24,45 @@ @Embeddable public class TorrentProgressComponent { - @ElementCollection - @CollectionTable - private final Map pieceToReceivedBlocks; - @Lob @Column(nullable = false) private final byte[] verifiedPieces; + @Lob + @Column(nullable = false) + private byte[] pieceToReceivedBlocks; + protected TorrentProgressComponent() { - this(Collections.emptyMap(), new byte[0]); + this(new byte[0], new byte[0]); } - public TorrentProgressComponent(Map pieceToReceivedBlocks, byte[] verifiedPieces) { + public TorrentProgressComponent(byte[] pieceToReceivedBlocks, byte[] verifiedPieces) { this.pieceToReceivedBlocks = pieceToReceivedBlocks; this.verifiedPieces = verifiedPieces; } public static TorrentProgressComponent fromDomain(TorrentProgress torrentProgress) { + byte[] pieceToReceivedBlocks = serializeMap(torrentProgress.getReceivedBlocks()); byte[] verifiedPieces = torrentProgress.getVerifiedPieces().toByteArray(); - Map pieceToReceivedBlocks = torrentProgress.getReceivedBlocks(); return new TorrentProgressComponent(pieceToReceivedBlocks, verifiedPieces); } + private static byte[] serializeMap(Map map) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos)) { + dos.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + dos.writeInt(entry.getKey()); + byte[] bitsetBytes = entry.getValue().toByteArray(); + dos.writeInt(bitsetBytes.length); + dos.write(bitsetBytes); + } + return baos.toByteArray(); + } catch (IOException e) { + throw new AssertionError(e); + } + } + public TorrentProgress toDomain(FileInfo fileInfo) { BitSet domainVerifiedPieces = BitSet.valueOf(verifiedPieces); Map domainFileProgress = fileInfo.getFileMetaData().stream() @@ -55,22 +74,42 @@ public TorrentProgress toDomain(FileInfo fileInfo) { domainVerifiedPieces) ) ); + Map domainPieceToReceivedBlocks = deserializeMap(pieceToReceivedBlocks); return TorrentProgress.createExisting(fileInfo, domainFileProgress, domainVerifiedPieces, - pieceToReceivedBlocks); + domainPieceToReceivedBlocks); } - public Map getPieceToReceivedBlocks() { - return pieceToReceivedBlocks; + private static Map deserializeMap(byte[] bytes) { + try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + DataInputStream dis = new DataInputStream(bais)) { + int size = dis.readInt(); + Map map = new HashMap<>(size); + for (int i = 0; i < size; i++) { + int key = dis.readInt(); + int length = dis.readInt(); + byte[] bitsetBytes = new byte[length]; + dis.readFully(bitsetBytes); + BitSet bitSet = BitSet.valueOf(bitsetBytes); + map.put(key, bitSet); + } + return map; + } catch (IOException e) { + throw new AssertionError(e); + } } public byte[] getVerifiedPieces() { return verifiedPieces; } + public byte[] getPieceToReceivedBlocks() { + return pieceToReceivedBlocks; + } + @Override public int hashCode() { - int result = pieceToReceivedBlocks.hashCode(); - result = 31 * result + Arrays.hashCode(verifiedPieces); + int result = Arrays.hashCode(verifiedPieces); + result = 31 * result + Arrays.hashCode(pieceToReceivedBlocks); return result; } @@ -84,14 +123,14 @@ public boolean equals(Object o) { } TorrentProgressComponent that = (TorrentProgressComponent) o; - return pieceToReceivedBlocks.equals(that.pieceToReceivedBlocks) - && Arrays.equals(verifiedPieces, that.verifiedPieces); + return Arrays.equals(verifiedPieces, that.verifiedPieces) + && Arrays.equals(pieceToReceivedBlocks, that.pieceToReceivedBlocks); } @Override public String toString() { return "TorrentProgressComponent{" - + ", pieceToReceivedBlocks=" + pieceToReceivedBlocks + + ", pieceToReceivedBlocks=" + Arrays.toString(pieceToReceivedBlocks) + ", verifiedPieces=" + Arrays.toString(verifiedPieces) + '}'; } From bc334840f9571850cb49bb45a1008a354d73daa7 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 2 Jun 2024 17:22:02 +0800 Subject: [PATCH 26/33] Format logback.xml --- src/main/resources/logback.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 44355aed..2628652d 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -24,7 +24,7 @@ - + From fb262650bd1d74be419eb70abe1014ba4351f1b2 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 2 Jun 2024 17:22:44 +0800 Subject: [PATCH 27/33] Parallelize file verification --- .../java/jtorrent/domain/torrent/handler/TorrentHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/jtorrent/domain/torrent/handler/TorrentHandler.java b/src/main/java/jtorrent/domain/torrent/handler/TorrentHandler.java index f083e7c7..f81d7d8a 100644 --- a/src/main/java/jtorrent/domain/torrent/handler/TorrentHandler.java +++ b/src/main/java/jtorrent/domain/torrent/handler/TorrentHandler.java @@ -127,6 +127,7 @@ private void verifyFiles() { torrent.resetCheckedBytes(); synchronized (pieceStateLock) { IntStream.range(0, torrent.getNumPieces()) + .parallel() .forEach(piece -> { if (isPieceChecksumValid(piece)) { torrent.setPieceVerified(piece); From a345050db9404541e7284c071487dee46d2ff279 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 2 Jun 2024 17:23:35 +0800 Subject: [PATCH 28/33] Update hibernate.properties --- src/main/resources/hibernate.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/hibernate.properties b/src/main/resources/hibernate.properties index 5bd9127c..f149ecc7 100644 --- a/src/main/resources/hibernate.properties +++ b/src/main/resources/hibernate.properties @@ -4,9 +4,10 @@ hibernate.connection.username=sa hibernate.connection.password= # Echo all executed SQL to console -hibernate.show_sql=true +hibernate.show_sql=false hibernate.format_sql=true hibernate.highlight_sql=true +hibernate.generate_statistics=false # Automatically export the schema hibernate.hbm2ddl.auto=update \ No newline at end of file From b8ec2a0267907694f2c21eed628c0a869cf4e0a2 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 2 Jun 2024 18:18:39 +0800 Subject: [PATCH 29/33] Revert accidental change --- .../data/torrent/source/db/model/FileInfoComponent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java b/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java index c256093e..da37dfdd 100644 --- a/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java +++ b/src/main/java/jtorrent/data/torrent/source/db/model/FileInfoComponent.java @@ -77,7 +77,7 @@ public static FileInfoComponent fromDomain(MultiFileInfo multiFileInfo) { byte[] pieceHashes = Sha1Hash.concatHashes(multiFileInfo.getPieceHashes()); int pieceSize = multiFileInfo.getPieceSize(); byte[] infoHash = multiFileInfo.getInfoHash().getBytes(); - return new FileInfoComponent(directory, List.of(), pieceHashes, pieceSize, infoHash); + return new FileInfoComponent(directory, fileMetadata, pieceHashes, pieceSize, infoHash); } public FileInfo toDomain() { From 84771361324a2dc64356d8808d2e94205dd191da Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 2 Jun 2024 18:49:02 +0800 Subject: [PATCH 30/33] Fix DataStatusBar not updating on first flipped bit --- .../java/jtorrent/domain/torrent/model/TorrentProgress.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java b/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java index 54b53393..d650d571 100644 --- a/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java +++ b/src/main/java/jtorrent/domain/torrent/model/TorrentProgress.java @@ -44,7 +44,7 @@ public TorrentProgress(FileInfo fileInfo, Map pathToFileProg this.checkedBytesSubject = BehaviorSubject.createDefault(checkedBytes); this.completePieces = completePieces; this.verifiedPieces = verifiedPieces; - this.verifiedPiecesSubject = BehaviorSubject.createDefault(verifiedPieces); + this.verifiedPiecesSubject = BehaviorSubject.createDefault((BitSet) verifiedPieces.clone()); this.pieceIndexToAvailableBlocks = pieceIndexToAvailableBlocks; this.partiallyMissingPieces = partiallyMissingPieces; this.partiallyMissingPiecesWithUnrequestedBlocks = partiallyMissingPiecesWithUnrequestedBlocks; From 80021bffb9c74a31e40bcebdc08735248db3251a Mon Sep 17 00:00:00 2001 From: Ashuh Date: Sun, 2 Jun 2024 20:32:11 +0800 Subject: [PATCH 31/33] Change db file nmae --- src/main/resources/hibernate.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/hibernate.properties b/src/main/resources/hibernate.properties index f149ecc7..a94b5cdb 100644 --- a/src/main/resources/hibernate.properties +++ b/src/main/resources/hibernate.properties @@ -1,5 +1,5 @@ # Database connection settings -hibernate.connection.url=jdbc:h2:file:file:./db;DB_CLOSE_DELAY=-1;AUTO_SERVER=TRUE +hibernate.connection.url=jdbc:h2:file:file:./jtorrent;DB_CLOSE_DELAY=-1;AUTO_SERVER=TRUE hibernate.connection.username=sa hibernate.connection.password= From 9e62a5079039ec514dde2e3ce414d013a19a8608 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Mon, 3 Jun 2024 00:40:23 +0800 Subject: [PATCH 32/33] Update TorrentEntityTest --- .../torrent/source/db/model/TorrentEntityTest.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/test/java/jtorrent/data/torrent/source/db/model/TorrentEntityTest.java b/src/test/java/jtorrent/data/torrent/source/db/model/TorrentEntityTest.java index ecd1a0df..c9b3d968 100644 --- a/src/test/java/jtorrent/data/torrent/source/db/model/TorrentEntityTest.java +++ b/src/test/java/jtorrent/data/torrent/source/db/model/TorrentEntityTest.java @@ -60,7 +60,6 @@ void bidirectionalMapping_singleFile() { .set(field("lastPiece"), 9) .set(field("lastPieceEnd"), 9) .set(field("start"), 0) - .set(field("end"), 99) .create(); SingleFileInfo fileInfo = Instancio.of(SingleFileInfo.class) @@ -71,9 +70,10 @@ void bidirectionalMapping_singleFile() { .create(); Torrent expected = Instancio.of(Torrent.class) - .assign(valueOf(field(TorrentMetadata.class, "trackers")) + .assign(valueOf(field(TorrentMetadata.class, "trackerTiers")) .to(field(Torrent.class, "trackers")) - .as(trackers -> ((Set) trackers) + .as(trackerTiers -> ((List>) trackerTiers) + .get(0) .stream() .map(TrackerFactory::fromUri) .collect(Collectors.toSet()) @@ -116,7 +116,6 @@ void bidirectionalMapping_multiFile() { .set(field("lastPiece"), 9) .set(field("lastPieceEnd"), 9) .set(field("start"), 0) - .set(field("end"), 99) .create(); FileMetadata fileMetadata2 = Instancio.of(FileMetadata.class) @@ -127,7 +126,6 @@ void bidirectionalMapping_multiFile() { .set(field("lastPiece"), 19) .set(field("lastPieceEnd"), 9) .set(field("start"), 100) - .set(field("end"), 199) .create(); MultiFileInfo fileInfo = Instancio.of(MultiFileInfo.class) @@ -143,9 +141,10 @@ void bidirectionalMapping_multiFile() { .create(); Torrent expected = Instancio.of(Torrent.class) - .assign(valueOf(field(TorrentMetadata.class, "trackers")) + .assign(valueOf(field(TorrentMetadata.class, "trackerTiers")) .to(field(Torrent.class, "trackers")) - .as(trackers -> ((Set) trackers) + .as(trackerTiers -> ((List>) trackerTiers) + .get(0) .stream() .map(TrackerFactory::fromUri) .collect(Collectors.toSet()) From 6c4e7e36042dfe3273986e80882d00ca077ec4c7 Mon Sep 17 00:00:00 2001 From: Ashuh Date: Mon, 3 Jun 2024 00:45:14 +0800 Subject: [PATCH 33/33] Update TorrentProgressComponentTest --- .../torrent/source/db/model/TorrentProgressComponentTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponentTest.java b/src/test/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponentTest.java index 1662ab8c..e81f6a56 100644 --- a/src/test/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponentTest.java +++ b/src/test/java/jtorrent/data/torrent/source/db/model/TorrentProgressComponentTest.java @@ -32,7 +32,6 @@ void bidirectionalMapping() { .set(field("lastPiece"), 4) .set(field("lastPieceEnd"), 8) .set(field("start"), 0) - .set(field("end"), 48) .create(); SingleFileInfo fileInfo = Instancio.of(SingleFileInfo.class)