diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 8fc45c99..c17cecc0 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -735,7 +735,7 @@ public class ArbitraryResource { @PathParam("name") String name, @QueryParam("title") String title, @QueryParam("description") String description, - @QueryParam("tags") String tags, + @QueryParam("tags") List tags, @QueryParam("category") Category category, String path) { Security.checkApiCallAllowed(request); @@ -780,7 +780,7 @@ public class ArbitraryResource { @PathParam("identifier") String identifier, @QueryParam("title") String title, @QueryParam("description") String description, - @QueryParam("tags") String tags, + @QueryParam("tags") List tags, @QueryParam("category") Category category, String path) { Security.checkApiCallAllowed(request); @@ -826,7 +826,7 @@ public class ArbitraryResource { @PathParam("name") String name, @QueryParam("title") String title, @QueryParam("description") String description, - @QueryParam("tags") String tags, + @QueryParam("tags") List tags, @QueryParam("category") Category category, String base64) { Security.checkApiCallAllowed(request); @@ -869,7 +869,7 @@ public class ArbitraryResource { @PathParam("identifier") String identifier, @QueryParam("title") String title, @QueryParam("description") String description, - @QueryParam("tags") String tags, + @QueryParam("tags") List tags, @QueryParam("category") Category category, String base64) { Security.checkApiCallAllowed(request); @@ -914,7 +914,7 @@ public class ArbitraryResource { @PathParam("name") String name, @QueryParam("title") String title, @QueryParam("description") String description, - @QueryParam("tags") String tags, + @QueryParam("tags") List tags, @QueryParam("category") Category category, String base64Zip) { Security.checkApiCallAllowed(request); @@ -957,7 +957,7 @@ public class ArbitraryResource { @PathParam("identifier") String identifier, @QueryParam("title") String title, @QueryParam("description") String description, - @QueryParam("tags") String tags, + @QueryParam("tags") List tags, @QueryParam("category") Category category, String base64Zip) { Security.checkApiCallAllowed(request); @@ -1005,7 +1005,7 @@ public class ArbitraryResource { @PathParam("name") String name, @QueryParam("title") String title, @QueryParam("description") String description, - @QueryParam("tags") String tags, + @QueryParam("tags") List tags, @QueryParam("category") Category category, String string) { Security.checkApiCallAllowed(request); @@ -1050,7 +1050,7 @@ public class ArbitraryResource { @PathParam("identifier") String identifier, @QueryParam("title") String title, @QueryParam("description") String description, - @QueryParam("tags") String tags, + @QueryParam("tags") List tags, @QueryParam("category") Category category, String string) { Security.checkApiCallAllowed(request); @@ -1068,7 +1068,7 @@ public class ArbitraryResource { private String upload(Service service, String name, String identifier, String path, String string, String base64, boolean zipped, - String title, String description, String tags, Category category) { + String title, String description, List tags, Category category) { // Fetch public key from registered name try (final Repository repository = RepositoryManager.getRepository()) { NameData nameData = repository.getNameRepository().fromName(name); diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataTransactionBuilder.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataTransactionBuilder.java index 93d1b0aa..13e5808c 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataTransactionBuilder.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataTransactionBuilder.java @@ -8,7 +8,6 @@ import org.qortal.arbitrary.ArbitraryDataDiff.*; import org.qortal.arbitrary.metadata.ArbitraryDataMetadataPatch; import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Service; -import org.qortal.block.BlockChain; import org.qortal.crypto.Crypto; import org.qortal.data.PaymentData; import org.qortal.data.transaction.ArbitraryTransactionData; @@ -55,7 +54,7 @@ public class ArbitraryDataTransactionBuilder { // Metadata private final String title; private final String description; - private final String tags; + private final List tags; private final Category category; private int chunkSize = ArbitraryDataFile.CHUNK_SIZE; @@ -65,7 +64,7 @@ public class ArbitraryDataTransactionBuilder { public ArbitraryDataTransactionBuilder(Repository repository, String publicKey58, Path path, String name, Method method, Service service, String identifier, - String title, String description, String tags, Category category) { + String title, String description, List tags, Category category) { this.repository = repository; this.publicKey58 = publicKey58; this.path = path; diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java index 20fb5467..33802d4f 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java @@ -29,6 +29,9 @@ import java.nio.file.Paths; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import java.util.Objects; public class ArbitraryDataWriter { @@ -45,13 +48,9 @@ public class ArbitraryDataWriter { // Metadata private final String title; private final String description; - private final String tags; + private final List tags; private final Category category; - private static int MAX_TITLE_LENGTH = 80; - private static int MAX_DESCRIPTION_LENGTH = 500; - private static int MAX_TAGS_LENGTH = 80; - private int chunkSize = ArbitraryDataFile.CHUNK_SIZE; private SecretKey aesKey; @@ -63,7 +62,7 @@ public class ArbitraryDataWriter { private Path encryptedPath; public ArbitraryDataWriter(Path filePath, String name, Service service, String identifier, Method method, Compression compression, - String title, String description, String tags, Category category) { + String title, String description, List tags, Category category) { this.filePath = filePath; this.name = name; this.service = service; @@ -77,9 +76,9 @@ public class ArbitraryDataWriter { this.identifier = identifier; // Metadata (optional) - this.title = title != null ? title.substring(0, Math.min(title.length(), MAX_TITLE_LENGTH)) : null; - this.description = description != null ? description.substring(0, Math.min(description.length(), MAX_DESCRIPTION_LENGTH)) : null; - this.tags = tags != null ? tags.substring(0, Math.min(tags.length(), MAX_TAGS_LENGTH)) : null; + this.title = ArbitraryDataTransactionMetadata.limitTitle(title); + this.description = ArbitraryDataTransactionMetadata.limitDescription(description); + this.tags = ArbitraryDataTransactionMetadata.limitTags(tags); this.category = category; } diff --git a/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataTransactionMetadata.java b/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataTransactionMetadata.java index e92f6828..1657fe05 100644 --- a/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataTransactionMetadata.java +++ b/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataTransactionMetadata.java @@ -9,6 +9,7 @@ import org.qortal.utils.Base58; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.List; public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata { @@ -16,9 +17,14 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata { private List chunks; private String title; private String description; - private String tags; + private List tags; private Category category; + private static int MAX_TITLE_LENGTH = 80; + private static int MAX_DESCRIPTION_LENGTH = 500; + private static int MAX_TAG_LENGTH = 20; + private static int MAX_TAGS_COUNT = 5; + public ArbitraryDataTransactionMetadata(Path filePath) { super(filePath); @@ -35,12 +41,25 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata { if (metadata.has("title")) { this.title = metadata.getString("title"); } + if (metadata.has("description")) { this.description = metadata.getString("description"); } + + List tagsList = new ArrayList<>(); if (metadata.has("tags")) { - this.tags = metadata.getString("tags"); + JSONArray tags = metadata.getJSONArray("tags"); + if (tags != null) { + for (int i=0; i tags) { this.tags = tags; } - public String getTags() { + public List getTags() { return this.tags; } @@ -139,4 +165,54 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata { return false; } + + // Static helper methods + + public static String limitTitle(String title) { + if (title == null) { + return null; + } + + return title.substring(0, Math.min(title.length(), MAX_TITLE_LENGTH)); + } + + public static String limitDescription(String description) { + if (description == null) { + return null; + } + + return description.substring(0, Math.min(description.length(), MAX_DESCRIPTION_LENGTH)); + } + + public static List limitTags(List tags) { + if (tags == null) { + return null; + } + + // Ensure tags list is mutable + List mutableTags = new ArrayList<>(tags); + + int tagCount = mutableTags.size(); + if (tagCount == 0) { + return null; + } + + // Remove tags over the limit + // This is cleaner than truncating, which results in malformed tags + Iterator iterator = mutableTags.iterator(); + while (iterator.hasNext()) { + String tag = (String) iterator.next(); + if (tag == null || tag.length() > MAX_TAG_LENGTH) { + iterator.remove(); + } + } + + // Limit the total number of tags + if (tagCount > MAX_TAGS_COUNT) { + mutableTags = mutableTags.subList(0, MAX_TAGS_COUNT); + } + + return mutableTags; + } + } diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java index 79ac499a..0500d999 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java @@ -23,10 +23,11 @@ import org.qortal.test.common.TransactionUtils; import org.qortal.test.common.transaction.TestTransaction; import org.qortal.transaction.RegisterNameTransaction; import org.qortal.utils.Base58; -import org.qortal.utils.NTP; import java.io.IOException; import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; import static org.junit.Assert.*; @@ -90,11 +91,12 @@ public class ArbitraryTransactionMetadataTests extends Common { String title = "Test title"; String description = "Test description"; - String tags = "Test tags"; + List tags = Arrays.asList("Test", "tag", "another tag"); Category category = Category.QORTAL; // Register the name to Alice RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); + transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp())); TransactionUtils.signAndMint(repository, transactionData, alice); // Create PUT transaction @@ -139,15 +141,16 @@ public class ArbitraryTransactionMetadataTests extends Common { String title = "title Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat pretium"; String description = "description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat pretium massa, non pulvinar mi pretium id. Ut gravida sapien vitae dui posuere tincidunt. Quisque in nibh est. Curabitur at blandit nunc, id aliquet neque. Nulla condimentum eget dolor a egestas. Vestibulum vel tincidunt ex. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras congue lacus in risus mattis suscipit. Quisque nisl eros, facilisis a lorem quis, vehicula bibendum."; - String tags = "tags Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat pretium"; + List tags = Arrays.asList("tag 1", "tag 2", "tag 3 that is longer than the 20 character limit", "tag 4", "tag 5", "tag 6", "tag 7"); Category category = Category.CRYPTOCURRENCY; String expectedTitle = "title Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat "; // 80 chars String expectedDescription = "description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat pretium massa, non pulvinar mi pretium id. Ut gravida sapien vitae dui posuere tincidunt. Quisque in nibh est. Curabitur at blandit nunc, id aliquet neque. Nulla condimentum eget dolor a egestas. Vestibulum vel tincidunt ex. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras congue lacus in risus mattis suscipit. Quisque nisl eros, facilisis a lorem quis, vehicula biben"; // 500 chars - String expectedTags = "tags Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat p"; // 80 chars + List expectedTags = Arrays.asList("tag 1", "tag 2", "tag 4", "tag 5", "tag 6"); // Register the name to Alice RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); + transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp())); TransactionUtils.signAndMint(repository, transactionData, alice); // Create PUT transaction diff --git a/src/test/java/org/qortal/test/common/ArbitraryUtils.java b/src/test/java/org/qortal/test/common/ArbitraryUtils.java index 592070c8..81abf47f 100644 --- a/src/test/java/org/qortal/test/common/ArbitraryUtils.java +++ b/src/test/java/org/qortal/test/common/ArbitraryUtils.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import java.util.Random; import static org.junit.Assert.assertEquals; @@ -32,8 +33,8 @@ public class ArbitraryUtils { } public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier, - ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account, - int chunkSize, String title, String description, String tags, Category category) throws DataException { + ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account, + int chunkSize, String title, String description, List tags, Category category) throws DataException { ArbitraryDataTransactionBuilder txnBuilder = new ArbitraryDataTransactionBuilder( repository, publicKey58, path, name, method, service, identifier, title, description, tags, category);