forked from Qortal/qortal
Include a list of files in the QDN metadata.
This commit is contained in:
parent
985c195e9e
commit
055775b13d
@ -23,16 +23,13 @@ import javax.crypto.NoSuchPaddingException;
|
|||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.*;
|
||||||
import java.nio.file.Path;
|
|
||||||
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.*;
|
||||||
import java.util.Iterator;
|
import java.util.stream.Collectors;
|
||||||
import java.util.List;
|
import java.util.stream.Stream;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class ArbitraryDataWriter {
|
public class ArbitraryDataWriter {
|
||||||
|
|
||||||
@ -50,6 +47,7 @@ public class ArbitraryDataWriter {
|
|||||||
private final String description;
|
private final String description;
|
||||||
private final List<String> tags;
|
private final List<String> tags;
|
||||||
private final Category category;
|
private final Category category;
|
||||||
|
private List<String> files;
|
||||||
|
|
||||||
private int chunkSize = ArbitraryDataFile.CHUNK_SIZE;
|
private int chunkSize = ArbitraryDataFile.CHUNK_SIZE;
|
||||||
|
|
||||||
@ -80,12 +78,14 @@ public class ArbitraryDataWriter {
|
|||||||
this.description = ArbitraryDataTransactionMetadata.limitDescription(description);
|
this.description = ArbitraryDataTransactionMetadata.limitDescription(description);
|
||||||
this.tags = ArbitraryDataTransactionMetadata.limitTags(tags);
|
this.tags = ArbitraryDataTransactionMetadata.limitTags(tags);
|
||||||
this.category = category;
|
this.category = category;
|
||||||
|
this.files = new ArrayList<>(); // Populated in buildFileList()
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save() throws IOException, DataException, InterruptedException, MissingDataException {
|
public void save() throws IOException, DataException, InterruptedException, MissingDataException {
|
||||||
try {
|
try {
|
||||||
this.preExecute();
|
this.preExecute();
|
||||||
this.validateService();
|
this.validateService();
|
||||||
|
this.buildFileList();
|
||||||
this.process();
|
this.process();
|
||||||
this.compress();
|
this.compress();
|
||||||
this.encrypt();
|
this.encrypt();
|
||||||
@ -143,6 +143,24 @@ public class ArbitraryDataWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void buildFileList() throws IOException {
|
||||||
|
// Single file resources consist of a single element in the file list
|
||||||
|
boolean isSingleFile = this.filePath.toFile().isFile();
|
||||||
|
if (isSingleFile) {
|
||||||
|
this.files.add(this.filePath.getFileName().toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi file resources require a walk through the directory tree
|
||||||
|
try (Stream<Path> stream = Files.walk(this.filePath)) {
|
||||||
|
this.files = stream
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.map(p -> this.filePath.relativize(p).toString())
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void process() throws DataException, IOException, MissingDataException {
|
private void process() throws DataException, IOException, MissingDataException {
|
||||||
switch (this.method) {
|
switch (this.method) {
|
||||||
|
|
||||||
@ -285,6 +303,7 @@ public class ArbitraryDataWriter {
|
|||||||
metadata.setTags(this.tags);
|
metadata.setTags(this.tags);
|
||||||
metadata.setCategory(this.category);
|
metadata.setCategory(this.category);
|
||||||
metadata.setChunks(this.arbitraryDataFile.chunkHashList());
|
metadata.setChunks(this.arbitraryDataFile.chunkHashList());
|
||||||
|
metadata.setFiles(this.files);
|
||||||
metadata.write();
|
metadata.write();
|
||||||
|
|
||||||
// Create an ArbitraryDataFile from the JSON file (we don't have a signature yet)
|
// Create an ArbitraryDataFile from the JSON file (we don't have a signature yet)
|
||||||
|
@ -19,6 +19,7 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
|
|||||||
private String description;
|
private String description;
|
||||||
private List<String> tags;
|
private List<String> tags;
|
||||||
private Category category;
|
private Category category;
|
||||||
|
private List<String> files;
|
||||||
|
|
||||||
private static int MAX_TITLE_LENGTH = 80;
|
private static int MAX_TITLE_LENGTH = 80;
|
||||||
private static int MAX_DESCRIPTION_LENGTH = 500;
|
private static int MAX_DESCRIPTION_LENGTH = 500;
|
||||||
@ -77,6 +78,20 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
|
|||||||
}
|
}
|
||||||
this.chunks = chunksList;
|
this.chunks = chunksList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> filesList = new ArrayList<>();
|
||||||
|
if (metadata.has("files")) {
|
||||||
|
JSONArray files = metadata.getJSONArray("files");
|
||||||
|
if (files != null) {
|
||||||
|
for (int i=0; i<files.length(); i++) {
|
||||||
|
String tag = files.getString(i);
|
||||||
|
if (tag != null) {
|
||||||
|
filesList.add(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.files = filesList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -111,6 +126,14 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
|
|||||||
}
|
}
|
||||||
outer.put("chunks", chunks);
|
outer.put("chunks", chunks);
|
||||||
|
|
||||||
|
JSONArray files = new JSONArray();
|
||||||
|
if (this.files != null) {
|
||||||
|
for (String file : this.files) {
|
||||||
|
files.put(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outer.put("files", files);
|
||||||
|
|
||||||
this.jsonString = outer.toString(2);
|
this.jsonString = outer.toString(2);
|
||||||
LOGGER.trace("Transaction metadata: {}", this.jsonString);
|
LOGGER.trace("Transaction metadata: {}", this.jsonString);
|
||||||
}
|
}
|
||||||
@ -156,6 +179,14 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
|
|||||||
return this.category;
|
return this.category;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFiles(List<String> files) {
|
||||||
|
this.files = files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getFiles() {
|
||||||
|
return this.files;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean containsChunk(byte[] chunk) {
|
public boolean containsChunk(byte[] chunk) {
|
||||||
for (byte[] c : this.chunks) {
|
for (byte[] c : this.chunks) {
|
||||||
if (Arrays.equals(c, chunk)) {
|
if (Arrays.equals(c, chunk)) {
|
||||||
|
@ -25,9 +25,13 @@ import org.qortal.transaction.RegisterNameTransaction;
|
|||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
@ -279,6 +283,74 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSingleFileList() throws DataException, IOException, MissingDataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
|
String publicKey58 = Base58.encode(alice.getPublicKey());
|
||||||
|
String name = "TEST"; // Can be anything for this test
|
||||||
|
String identifier = null; // Not used for this test
|
||||||
|
Service service = Service.ARBITRARY_DATA;
|
||||||
|
int chunkSize = 100;
|
||||||
|
int dataLength = 900; // Actual data length will be longer due to encryption
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Add a few files at multiple levels
|
||||||
|
byte[] data = new byte[1024];
|
||||||
|
new Random().nextBytes(data);
|
||||||
|
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||||
|
Path file1 = Paths.get(path1.toString(), "file.txt");
|
||||||
|
|
||||||
|
// Create PUT transaction
|
||||||
|
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, file1, name, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize);
|
||||||
|
|
||||||
|
// Check the file list metadata is correct
|
||||||
|
assertEquals(1, arbitraryDataFile.getMetadata().getFiles().size());
|
||||||
|
assertTrue(arbitraryDataFile.getMetadata().getFiles().contains("file.txt"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleFileList() throws DataException, IOException, MissingDataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
|
String publicKey58 = Base58.encode(alice.getPublicKey());
|
||||||
|
String name = "TEST"; // Can be anything for this test
|
||||||
|
String identifier = null; // Not used for this test
|
||||||
|
Service service = Service.ARBITRARY_DATA;
|
||||||
|
int chunkSize = 100;
|
||||||
|
int dataLength = 900; // Actual data length will be longer due to encryption
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Add a few files at multiple levels
|
||||||
|
byte[] data = new byte[1024];
|
||||||
|
new Random().nextBytes(data);
|
||||||
|
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||||
|
Files.write(Paths.get(path1.toString(), "image1.jpg"), data, StandardOpenOption.CREATE);
|
||||||
|
|
||||||
|
Path subdirectory = Paths.get(path1.toString(), "subdirectory");
|
||||||
|
Files.createDirectories(subdirectory);
|
||||||
|
Files.write(Paths.get(subdirectory.toString(), "config.json"), data, StandardOpenOption.CREATE);
|
||||||
|
|
||||||
|
// Create PUT transaction
|
||||||
|
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize);
|
||||||
|
|
||||||
|
// Check the file list metadata is correct
|
||||||
|
assertEquals(3, arbitraryDataFile.getMetadata().getFiles().size());
|
||||||
|
assertTrue(arbitraryDataFile.getMetadata().getFiles().contains("file.txt"));
|
||||||
|
assertTrue(arbitraryDataFile.getMetadata().getFiles().contains("image1.jpg"));
|
||||||
|
assertTrue(arbitraryDataFile.getMetadata().getFiles().contains("subdirectory/config.json"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExistingCategories() {
|
public void testExistingCategories() {
|
||||||
// Matching categories should be correctly located
|
// Matching categories should be correctly located
|
||||||
|
Loading…
x
Reference in New Issue
Block a user