Tags now use an array of strings, rather than a single string.

This commit is contained in:
CalDescent 2022-02-26 22:09:07 +00:00
parent a5c11d4c23
commit dc41dc4c69
6 changed files with 111 additions and 33 deletions

View File

@ -735,7 +735,7 @@ public class ArbitraryResource {
@PathParam("name") String name, @PathParam("name") String name,
@QueryParam("title") String title, @QueryParam("title") String title,
@QueryParam("description") String description, @QueryParam("description") String description,
@QueryParam("tags") String tags, @QueryParam("tags") List<String> tags,
@QueryParam("category") Category category, @QueryParam("category") Category category,
String path) { String path) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
@ -780,7 +780,7 @@ public class ArbitraryResource {
@PathParam("identifier") String identifier, @PathParam("identifier") String identifier,
@QueryParam("title") String title, @QueryParam("title") String title,
@QueryParam("description") String description, @QueryParam("description") String description,
@QueryParam("tags") String tags, @QueryParam("tags") List<String> tags,
@QueryParam("category") Category category, @QueryParam("category") Category category,
String path) { String path) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
@ -826,7 +826,7 @@ public class ArbitraryResource {
@PathParam("name") String name, @PathParam("name") String name,
@QueryParam("title") String title, @QueryParam("title") String title,
@QueryParam("description") String description, @QueryParam("description") String description,
@QueryParam("tags") String tags, @QueryParam("tags") List<String> tags,
@QueryParam("category") Category category, @QueryParam("category") Category category,
String base64) { String base64) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
@ -869,7 +869,7 @@ public class ArbitraryResource {
@PathParam("identifier") String identifier, @PathParam("identifier") String identifier,
@QueryParam("title") String title, @QueryParam("title") String title,
@QueryParam("description") String description, @QueryParam("description") String description,
@QueryParam("tags") String tags, @QueryParam("tags") List<String> tags,
@QueryParam("category") Category category, @QueryParam("category") Category category,
String base64) { String base64) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
@ -914,7 +914,7 @@ public class ArbitraryResource {
@PathParam("name") String name, @PathParam("name") String name,
@QueryParam("title") String title, @QueryParam("title") String title,
@QueryParam("description") String description, @QueryParam("description") String description,
@QueryParam("tags") String tags, @QueryParam("tags") List<String> tags,
@QueryParam("category") Category category, @QueryParam("category") Category category,
String base64Zip) { String base64Zip) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
@ -957,7 +957,7 @@ public class ArbitraryResource {
@PathParam("identifier") String identifier, @PathParam("identifier") String identifier,
@QueryParam("title") String title, @QueryParam("title") String title,
@QueryParam("description") String description, @QueryParam("description") String description,
@QueryParam("tags") String tags, @QueryParam("tags") List<String> tags,
@QueryParam("category") Category category, @QueryParam("category") Category category,
String base64Zip) { String base64Zip) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
@ -1005,7 +1005,7 @@ public class ArbitraryResource {
@PathParam("name") String name, @PathParam("name") String name,
@QueryParam("title") String title, @QueryParam("title") String title,
@QueryParam("description") String description, @QueryParam("description") String description,
@QueryParam("tags") String tags, @QueryParam("tags") List<String> tags,
@QueryParam("category") Category category, @QueryParam("category") Category category,
String string) { String string) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
@ -1050,7 +1050,7 @@ public class ArbitraryResource {
@PathParam("identifier") String identifier, @PathParam("identifier") String identifier,
@QueryParam("title") String title, @QueryParam("title") String title,
@QueryParam("description") String description, @QueryParam("description") String description,
@QueryParam("tags") String tags, @QueryParam("tags") List<String> tags,
@QueryParam("category") Category category, @QueryParam("category") Category category,
String string) { String string) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
@ -1068,7 +1068,7 @@ public class ArbitraryResource {
private String upload(Service service, String name, String identifier, private String upload(Service service, String name, String identifier,
String path, String string, String base64, boolean zipped, String path, String string, String base64, boolean zipped,
String title, String description, String tags, Category category) { String title, String description, List<String> tags, Category category) {
// Fetch public key from registered name // Fetch public key from registered name
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
NameData nameData = repository.getNameRepository().fromName(name); NameData nameData = repository.getNameRepository().fromName(name);

View File

@ -8,7 +8,6 @@ import org.qortal.arbitrary.ArbitraryDataDiff.*;
import org.qortal.arbitrary.metadata.ArbitraryDataMetadataPatch; import org.qortal.arbitrary.metadata.ArbitraryDataMetadataPatch;
import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Category;
import org.qortal.arbitrary.misc.Service; import org.qortal.arbitrary.misc.Service;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.PaymentData; import org.qortal.data.PaymentData;
import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.ArbitraryTransactionData;
@ -55,7 +54,7 @@ public class ArbitraryDataTransactionBuilder {
// Metadata // Metadata
private final String title; private final String title;
private final String description; private final String description;
private final String tags; private final List<String> tags;
private final Category category; private final Category category;
private int chunkSize = ArbitraryDataFile.CHUNK_SIZE; private int chunkSize = ArbitraryDataFile.CHUNK_SIZE;
@ -65,7 +64,7 @@ public class ArbitraryDataTransactionBuilder {
public ArbitraryDataTransactionBuilder(Repository repository, String publicKey58, Path path, String name, public ArbitraryDataTransactionBuilder(Repository repository, String publicKey58, Path path, String name,
Method method, Service service, String identifier, Method method, Service service, String identifier,
String title, String description, String tags, Category category) { String title, String description, List<String> tags, Category category) {
this.repository = repository; this.repository = repository;
this.publicKey58 = publicKey58; this.publicKey58 = publicKey58;
this.path = path; this.path = path;

View File

@ -29,6 +29,9 @@ import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects; import java.util.Objects;
public class ArbitraryDataWriter { public class ArbitraryDataWriter {
@ -45,13 +48,9 @@ public class ArbitraryDataWriter {
// Metadata // Metadata
private final String title; private final String title;
private final String description; private final String description;
private final String tags; private final List<String> tags;
private final Category category; 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 int chunkSize = ArbitraryDataFile.CHUNK_SIZE;
private SecretKey aesKey; private SecretKey aesKey;
@ -63,7 +62,7 @@ public class ArbitraryDataWriter {
private Path encryptedPath; private Path encryptedPath;
public ArbitraryDataWriter(Path filePath, String name, Service service, String identifier, Method method, Compression compression, 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<String> tags, Category category) {
this.filePath = filePath; this.filePath = filePath;
this.name = name; this.name = name;
this.service = service; this.service = service;
@ -77,9 +76,9 @@ public class ArbitraryDataWriter {
this.identifier = identifier; this.identifier = identifier;
// Metadata (optional) // Metadata (optional)
this.title = title != null ? title.substring(0, Math.min(title.length(), MAX_TITLE_LENGTH)) : null; this.title = ArbitraryDataTransactionMetadata.limitTitle(title);
this.description = description != null ? description.substring(0, Math.min(description.length(), MAX_DESCRIPTION_LENGTH)) : null; this.description = ArbitraryDataTransactionMetadata.limitDescription(description);
this.tags = tags != null ? tags.substring(0, Math.min(tags.length(), MAX_TAGS_LENGTH)) : null; this.tags = ArbitraryDataTransactionMetadata.limitTags(tags);
this.category = category; this.category = category;
} }

View File

@ -9,6 +9,7 @@ import org.qortal.utils.Base58;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator;
import java.util.List; import java.util.List;
public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata { public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
@ -16,9 +17,14 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
private List<byte[]> chunks; private List<byte[]> chunks;
private String title; private String title;
private String description; private String description;
private String tags; private List<String> tags;
private Category category; 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) { public ArbitraryDataTransactionMetadata(Path filePath) {
super(filePath); super(filePath);
@ -35,12 +41,25 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
if (metadata.has("title")) { if (metadata.has("title")) {
this.title = metadata.getString("title"); this.title = metadata.getString("title");
} }
if (metadata.has("description")) { if (metadata.has("description")) {
this.description = metadata.getString("description"); this.description = metadata.getString("description");
} }
List<String> tagsList = new ArrayList<>();
if (metadata.has("tags")) { if (metadata.has("tags")) {
this.tags = metadata.getString("tags"); JSONArray tags = metadata.getJSONArray("tags");
if (tags != null) {
for (int i=0; i<tags.length(); i++) {
String tag = tags.getString(i);
if (tag != null) {
tagsList.add(tag);
} }
}
}
this.tags = tagsList;
}
if (metadata.has("category")) { if (metadata.has("category")) {
this.category = Category.uncategorizedValueOf(metadata.getString("category")); this.category = Category.uncategorizedValueOf(metadata.getString("category"));
} }
@ -67,12 +86,19 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
if (this.title != null && !this.title.isEmpty()) { if (this.title != null && !this.title.isEmpty()) {
outer.put("title", this.title); outer.put("title", this.title);
} }
if (this.description != null && !this.description.isEmpty()) { if (this.description != null && !this.description.isEmpty()) {
outer.put("description", this.description); outer.put("description", this.description);
} }
if (this.tags != null && !this.tags.isEmpty()) {
outer.put("tags", this.tags); JSONArray tags = new JSONArray();
if (this.tags != null) {
for (String tag : this.tags) {
tags.put(tag);
} }
outer.put("tags", tags);
}
if (this.category != null) { if (this.category != null) {
outer.put("category", this.category.toString()); outer.put("category", this.category.toString());
} }
@ -114,11 +140,11 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
return this.description; return this.description;
} }
public void setTags(String tags) { public void setTags(List<String> tags) {
this.tags = tags; this.tags = tags;
} }
public String getTags() { public List<String> getTags() {
return this.tags; return this.tags;
} }
@ -139,4 +165,54 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
return false; 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<String> limitTags(List<String> tags) {
if (tags == null) {
return null;
}
// Ensure tags list is mutable
List<String> 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;
}
} }

View File

@ -23,10 +23,11 @@ import org.qortal.test.common.TransactionUtils;
import org.qortal.test.common.transaction.TestTransaction; import org.qortal.test.common.transaction.TestTransaction;
import org.qortal.transaction.RegisterNameTransaction; import org.qortal.transaction.RegisterNameTransaction;
import org.qortal.utils.Base58; import org.qortal.utils.Base58;
import org.qortal.utils.NTP;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -90,11 +91,12 @@ public class ArbitraryTransactionMetadataTests extends Common {
String title = "Test title"; String title = "Test title";
String description = "Test description"; String description = "Test description";
String tags = "Test tags"; List<String> tags = Arrays.asList("Test", "tag", "another tag");
Category category = Category.QORTAL; Category category = Category.QORTAL;
// Register the name to Alice // Register the name to Alice
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "");
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
TransactionUtils.signAndMint(repository, transactionData, alice); TransactionUtils.signAndMint(repository, transactionData, alice);
// Create PUT transaction // 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 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 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<String> 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; Category category = Category.CRYPTOCURRENCY;
String expectedTitle = "title Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat "; // 80 chars 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 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<String> expectedTags = Arrays.asList("tag 1", "tag 2", "tag 4", "tag 5", "tag 6");
// Register the name to Alice // Register the name to Alice
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "");
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
TransactionUtils.signAndMint(repository, transactionData, alice); TransactionUtils.signAndMint(repository, transactionData, alice);
// Create PUT transaction // Create PUT transaction

View File

@ -17,6 +17,7 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List;
import java.util.Random; import java.util.Random;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -33,7 +34,7 @@ public class ArbitraryUtils {
public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier, public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier,
ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account, ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account,
int chunkSize, String title, String description, String tags, Category category) throws DataException { int chunkSize, String title, String description, List<String> tags, Category category) throws DataException {
ArbitraryDataTransactionBuilder txnBuilder = new ArbitraryDataTransactionBuilder( ArbitraryDataTransactionBuilder txnBuilder = new ArbitraryDataTransactionBuilder(
repository, publicKey58, path, name, method, service, identifier, title, description, tags, category); repository, publicKey58, path, name, method, service, identifier, title, description, tags, category);