3
0
mirror of https://github.com/Qortal/qortal.git synced 2025-02-14 11:15:49 +00:00

Simplified lists feature to be referenced by a single name, instead of the confusing "category" and "resourceName" properties.

This makes them extremely generic, improves filenames, and makes it easier to create custom lists. It doesn't have backwards support, but the lists feature isn't working properly in core 2.1+ anyway.
This commit is contained in:
CalDescent 2021-12-10 11:33:26 +00:00
parent 0001f31c06
commit 0464245218
9 changed files with 78 additions and 125 deletions

View File

@ -33,10 +33,9 @@ public class ListsResource {
@POST
@Path("/{category}/{resourceName}")
@Path("/{listName}")
@Operation(
summary = "Add items to a new or existing list",
description = "Example categories are 'blacklist' or 'followed'. Example resource names are 'addresses' or 'names'",
requestBody = @RequestBody(
required = true,
content = @Content(
@ -57,12 +56,11 @@ public class ListsResource {
)
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
@SecurityRequirement(name = "apiKey")
public String addItemstoList(@PathParam("category") String category,
@PathParam("resourceName") String resourceName,
public String addItemstoList(@PathParam("listName") String listName,
ListRequest listRequest) {
Security.checkApiCallAllowed(request);
if (category == null || resourceName == null) {
if (listName == null) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
@ -73,64 +71,33 @@ public class ListsResource {
int successCount = 0;
int errorCount = 0;
try (final Repository repository = RepositoryManager.getRepository()) {
for (String item : listRequest.items) {
for (String item : listRequest.items) {
// Validate addresses
if (resourceName.equals("address") || resourceName.equals("addresses")) {
if (!Crypto.isValidAddress(item)) {
errorCount++;
continue;
}
AccountData accountData = repository.getAccountRepository().getAccount(item);
// Not found?
if (accountData == null) {
errorCount++;
continue;
}
}
// Validate names
if (resourceName.equals("name") || resourceName.equals("names")) {
if (!repository.getNameRepository().nameExists(item)) {
errorCount++;
continue;
}
}
// Valid address, so go ahead and blacklist it
boolean success = ResourceListManager.getInstance().addToList(category, resourceName, item, false);
if (success) {
successCount++;
}
else {
errorCount++;
}
boolean success = ResourceListManager.getInstance().addToList(listName, item, false);
if (success) {
successCount++;
}
else {
errorCount++;
}
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
if (successCount > 0 && errorCount == 0) {
// All were successful, so save the blacklist
ResourceListManager.getInstance().saveList(category, resourceName);
// All were successful, so save the list
ResourceListManager.getInstance().saveList(listName);
return "true";
}
else {
// Something went wrong, so revert
ResourceListManager.getInstance().revertList(category, resourceName);
ResourceListManager.getInstance().revertList(listName);
return "false";
}
}
@DELETE
@Path("/{category}/{resourceName}")
@Path("/{listName}")
@Operation(
summary = "Remove one or more items from a list",
description = "Example categories are 'blacklist' or 'followed'. Example resource names are 'addresses' or 'names'",
requestBody = @RequestBody(
required = true,
content = @Content(
@ -151,8 +118,7 @@ public class ListsResource {
)
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
@SecurityRequirement(name = "apiKey")
public String removeItemsFromList(@PathParam("category") String category,
@PathParam("resourceName") String resourceName,
public String removeItemsFromList(@PathParam("listName") String listName,
ListRequest listRequest) {
Security.checkApiCallAllowed(request);
@ -167,7 +133,7 @@ public class ListsResource {
// Attempt to remove the item
// Don't save as we will do this at the end of the process
boolean success = ResourceListManager.getInstance().removeFromList(category, resourceName, address, false);
boolean success = ResourceListManager.getInstance().removeFromList(listName, address, false);
if (success) {
successCount++;
}
@ -177,22 +143,21 @@ public class ListsResource {
}
if (successCount > 0 && errorCount == 0) {
// All were successful, so save the blacklist
ResourceListManager.getInstance().saveList(category, resourceName);
// All were successful, so save the list
ResourceListManager.getInstance().saveList(listName);
return "true";
}
else {
// Something went wrong, so revert
ResourceListManager.getInstance().revertList(category, resourceName);
ResourceListManager.getInstance().revertList(listName);
return "false";
}
}
@GET
@Path("/{category}/{resourceName}")
@Path("/{listName}")
@Operation(
summary = "Fetch all items in a list",
description = "Example categories are 'blacklist' or 'followed'. Example resource names are 'addresses' or 'names'",
responses = {
@ApiResponse(
description = "A JSON array of items",
@ -201,10 +166,9 @@ public class ListsResource {
}
)
@SecurityRequirement(name = "apiKey")
public String getItemsInList(@PathParam("category") String category,
@PathParam("resourceName") String resourceName) {
public String getItemsInList(@PathParam("listName") String listName) {
Security.checkApiCallAllowed(request);
return ResourceListManager.getInstance().getJSONStringForList(category, resourceName);
return ResourceListManager.getInstance().getJSONStringForList(listName);
}
}

View File

@ -43,7 +43,7 @@ public class ArbitraryDataResource {
// Check if the name is blacklisted
if (ResourceListManager.getInstance()
.listContains("blacklist", "names", this.resourceId, false)) {
.listContains("blacklistedNames", this.resourceId, false)) {
return new ArbitraryResourceSummary(ArbitraryResourceStatus.BLACKLISTED);
}

View File

@ -142,7 +142,7 @@ public class ArbitraryDataManager extends Thread {
private void processNames() {
// Fetch latest list of followed names
List<String> followedNames = ResourceListManager.getInstance().getStringsInList("followed", "names");
List<String> followedNames = ResourceListManager.getInstance().getStringsInList("followedNames");
if (followedNames == null || followedNames.isEmpty()) {
return;
}

View File

@ -223,19 +223,19 @@ public class ArbitraryDataStorageManager extends Thread {
}
public boolean isNameInBlacklist(String name) {
return ResourceListManager.getInstance().listContains("blacklist", "names", name, false);
return ResourceListManager.getInstance().listContains("blacklistedNames", name, false);
}
private boolean isFollowingName(String name) {
return ResourceListManager.getInstance().listContains("followed", "names", name, false);
return ResourceListManager.getInstance().listContains("followedNames", name, false);
}
public List<String> followedNames() {
return ResourceListManager.getInstance().getStringsInList("followed", "names");
return ResourceListManager.getInstance().getStringsInList("followedNames");
}
private int followedNamesCount() {
return ResourceListManager.getInstance().getItemCountForList("followed", "names");
return ResourceListManager.getInstance().getItemCountForList("followedNames");
}

View File

@ -19,8 +19,7 @@ public class ResourceList {
private static final Logger LOGGER = LogManager.getLogger(ResourceList.class);
private String category;
private String resourceName;
private String name;
private List<String> list = new ArrayList<>();
/**
@ -29,13 +28,11 @@ public class ResourceList {
* This can be used for local blocking, or even for curating and sharing content lists
* Lists are backed off to JSON files (in the lists folder) to ease sharing between nodes and users
*
* @param category - for instance "blacklist", "whitelist", or "userlist"
* @param resourceName - for instance "address", "poll", or "group"
* @param name - the name of the list, for instance "addressblacklist"
* @throws IOException
*/
public ResourceList(String category, String resourceName) throws IOException {
this.category = category;
this.resourceName = resourceName;
public ResourceList(String name) throws IOException {
this.name = name;
this.load();
}
@ -43,17 +40,14 @@ public class ResourceList {
/* Filesystem */
private Path getFilePath() {
String pathString = String.format("%s%s%s_%s.json", Settings.getInstance().getListsPath(),
File.separator, this.category, this.resourceName);
String pathString = String.format("%s%s%s.json", Settings.getInstance().getListsPath(),
File.separator, this.name);
return Paths.get(pathString);
}
public void save() throws IOException {
if (this.resourceName == null) {
throw new IllegalStateException("Can't save list with missing resource name");
}
if (this.category == null) {
throw new IllegalStateException("Can't save list with missing category");
if (this.name == null) {
throw new IllegalStateException("Can't save list with missing name");
}
String jsonString = ResourceList.listToJSONString(this.list);
Path filePath = this.getFilePath();
@ -91,7 +85,7 @@ public class ResourceList {
try {
return this.load();
} catch (IOException e) {
LOGGER.info("Unable to revert {} {}", this.resourceName, this.category);
LOGGER.info("Unable to revert list {}: {}", this.name, e.getMessage());
}
return false;
}
@ -159,12 +153,8 @@ public class ResourceList {
return ResourceList.listToJSONString(this.list);
}
public String getCategory() {
return this.category;
}
public String getResourceName() {
return this.resourceName;
public String getName() {
return this.name;
}
public List<String> getList() {
@ -172,7 +162,7 @@ public class ResourceList {
}
public String toString() {
return String.format("%s %s", this.category, this.resourceName);
return this.name;
}
}

View File

@ -26,10 +26,9 @@ public class ResourceListManager {
return instance;
}
private ResourceList getList(String category, String resourceName) {
private ResourceList getList(String listName) {
for (ResourceList list : this.lists) {
if (Objects.equals(list.getCategory(), category) &&
Objects.equals(list.getResourceName(), resourceName)) {
if (Objects.equals(list.getName(), listName)) {
return list;
}
}
@ -37,19 +36,19 @@ public class ResourceListManager {
// List doesn't exist in array yet, so create it
// This will load any existing data from the filesystem
try {
ResourceList list = new ResourceList(category, resourceName);
ResourceList list = new ResourceList(listName);
this.lists.add(list);
return list;
} catch (IOException e) {
LOGGER.info("Unable to load or create list {} {}: {}", category, resourceName, e.getMessage());
LOGGER.info("Unable to load or create list {}: {}", listName, e.getMessage());
return null;
}
}
public boolean addToList(String category, String resourceName, String item, boolean save) {
ResourceList list = this.getList(category, resourceName);
public boolean addToList(String listName, String item, boolean save) {
ResourceList list = this.getList(listName);
if (list == null) {
return false;
}
@ -67,8 +66,8 @@ public class ResourceListManager {
}
}
public boolean removeFromList(String category, String resourceName, String item, boolean save) {
ResourceList list = this.getList(category, resourceName);
public boolean removeFromList(String listName, String item, boolean save) {
ResourceList list = this.getList(listName);
if (list == null) {
return false;
}
@ -87,16 +86,16 @@ public class ResourceListManager {
}
}
public boolean listContains(String category, String resourceName, String item, boolean caseSensitive) {
ResourceList list = this.getList(category, resourceName);
public boolean listContains(String listName, String item, boolean caseSensitive) {
ResourceList list = this.getList(listName);
if (list == null) {
return false;
}
return list.contains(item, caseSensitive);
}
public void saveList(String category, String resourceName) {
ResourceList list = this.getList(category, resourceName);
public void saveList(String listName) {
ResourceList list = this.getList(listName);
if (list == null) {
return;
}
@ -109,32 +108,32 @@ public class ResourceListManager {
}
}
public void revertList(String category, String resourceName) {
ResourceList list = this.getList(category, resourceName);
public void revertList(String listName) {
ResourceList list = this.getList(listName);
if (list == null) {
return;
}
list.revert();
}
public String getJSONStringForList(String category, String resourceName) {
ResourceList list = this.getList(category, resourceName);
public String getJSONStringForList(String listName) {
ResourceList list = this.getList(listName);
if (list == null) {
return null;
}
return list.getJSONString();
}
public List<String> getStringsInList(String category, String resourceName) {
ResourceList list = this.getList(category, resourceName);
public List<String> getStringsInList(String listName) {
ResourceList list = this.getList(listName);
if (list == null) {
return null;
}
return list.getList();
}
public int getItemCountForList(String category, String resourceName) {
ResourceList list = this.getList(category, resourceName);
public int getItemCountForList(String listName) {
ResourceList list = this.getList(listName);
if (list == null) {
return 0;
}

View File

@ -147,7 +147,7 @@ public class ChatTransaction extends Transaction {
// Check for blacklisted author by address
ResourceListManager listManager = ResourceListManager.getInstance();
if (listManager.listContains("blacklist", "addresses", this.chatTransactionData.getSender(), true)) {
if (listManager.listContains("blacklistedAddresses", this.chatTransactionData.getSender(), true)) {
return ValidationResult.ADDRESS_IN_BLACKLIST;
}
@ -156,7 +156,7 @@ public class ChatTransaction extends Transaction {
if (names != null && names.size() > 0) {
for (NameData nameData : names) {
if (nameData != null && nameData.getName() != null) {
if (listManager.listContains("blacklist", "names", nameData.getName(), false)) {
if (listManager.listContains("blacklistedNames", nameData.getName(), false)) {
return ValidationResult.NAME_IN_BLACKLIST;
}
}

View File

@ -96,18 +96,18 @@ public class ArbitraryDataStorageCapacityTests extends Common {
assertTrue(storageManager.isStorageCapacityCalculated());
// Storage capacity should initially equal the total
assertEquals(0, resourceListManager.getItemCountForList("followed", "names"));
assertEquals(0, resourceListManager.getItemCountForList("followedNames"));
long totalStorageCapacity = storageManager.getStorageCapacityIncludingThreshold(storageFullThreshold);
assertEquals(totalStorageCapacity, storageManager.storageCapacityPerName(storageFullThreshold));
// Follow some names
assertTrue(resourceListManager.addToList("followed", "names", "Test1", false));
assertTrue(resourceListManager.addToList("followed", "names", "Test2", false));
assertTrue(resourceListManager.addToList("followed", "names", "Test3", false));
assertTrue(resourceListManager.addToList("followed", "names", "Test4", false));
assertTrue(resourceListManager.addToList("followedNames", "Test1", false));
assertTrue(resourceListManager.addToList("followedNames", "Test2", false));
assertTrue(resourceListManager.addToList("followedNames", "Test3", false));
assertTrue(resourceListManager.addToList("followedNames", "Test4", false));
// Ensure the followed name count is correct
assertEquals(4, resourceListManager.getItemCountForList("followed", "names"));
assertEquals(4, resourceListManager.getItemCountForList("followedNames"));
// Storage space per name should be the total storage capacity divided by the number of names
long expectedStorageCapacityPerName = (long)(totalStorageCapacity / 4.0f);

View File

@ -65,7 +65,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
ArbitraryTransactionData transactionData = this.createTxnWithName(repository, alice, name);
// Add name to followed list
assertTrue(ResourceListManager.getInstance().addToList("followed", "names", name, false));
assertTrue(ResourceListManager.getInstance().addToList("followedNames", name, false));
// We should store and pre-fetch data for this transaction
assertEquals(StoragePolicy.FOLLOWED_AND_VIEWED, Settings.getInstance().getStoragePolicy());
@ -73,7 +73,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
assertTrue(storageManager.shouldPreFetchData(repository, transactionData));
// Now unfollow the name
assertTrue(ResourceListManager.getInstance().removeFromList("followed", "names", name, false));
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
// We should store but not pre-fetch data for this transaction
assertTrue(storageManager.canStoreData(transactionData));
@ -98,7 +98,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
ArbitraryTransactionData transactionData = this.createTxnWithName(repository, alice, name);
// Add name to followed list
assertTrue(ResourceListManager.getInstance().addToList("followed", "names", name, false));
assertTrue(ResourceListManager.getInstance().addToList("followedNames", name, false));
// We should store and pre-fetch data for this transaction
assertEquals(StoragePolicy.FOLLOWED, Settings.getInstance().getStoragePolicy());
@ -106,7 +106,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
assertTrue(storageManager.shouldPreFetchData(repository, transactionData));
// Now unfollow the name
assertTrue(ResourceListManager.getInstance().removeFromList("followed", "names", name, false));
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
// We shouldn't store or pre-fetch data for this transaction
assertFalse(storageManager.canStoreData(transactionData));
@ -131,7 +131,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
ArbitraryTransactionData transactionData = this.createTxnWithName(repository, alice, name);
// Add name to followed list
assertTrue(ResourceListManager.getInstance().addToList("followed", "names", name, false));
assertTrue(ResourceListManager.getInstance().addToList("followedNames", name, false));
// We should store but not pre-fetch data for this transaction
assertEquals(StoragePolicy.VIEWED, Settings.getInstance().getStoragePolicy());
@ -139,7 +139,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
assertFalse(storageManager.shouldPreFetchData(repository, transactionData));
// Now unfollow the name
assertTrue(ResourceListManager.getInstance().removeFromList("followed", "names", name, false));
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
// We should store but not pre-fetch data for this transaction
assertTrue(storageManager.canStoreData(transactionData));
@ -164,7 +164,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
ArbitraryTransactionData transactionData = this.createTxnWithName(repository, alice, name);
// Add name to followed list
assertTrue(ResourceListManager.getInstance().addToList("followed", "names", name, false));
assertTrue(ResourceListManager.getInstance().addToList("followedNames", name, false));
// We should store and pre-fetch data for this transaction
assertEquals(StoragePolicy.ALL, Settings.getInstance().getStoragePolicy());
@ -172,7 +172,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
assertTrue(storageManager.shouldPreFetchData(repository, transactionData));
// Now unfollow the name
assertTrue(ResourceListManager.getInstance().removeFromList("followed", "names", name, false));
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
// We should store and pre-fetch data for this transaction
assertTrue(storageManager.canStoreData(transactionData));
@ -197,7 +197,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
ArbitraryTransactionData transactionData = this.createTxnWithName(repository, alice, name);
// Add name to followed list
assertTrue(ResourceListManager.getInstance().addToList("followed", "names", name, false));
assertTrue(ResourceListManager.getInstance().addToList("followedNames", name, false));
// We shouldn't store or pre-fetch data for this transaction
assertEquals(StoragePolicy.NONE, Settings.getInstance().getStoragePolicy());
@ -205,7 +205,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
assertFalse(storageManager.shouldPreFetchData(repository, transactionData));
// Now unfollow the name
assertTrue(ResourceListManager.getInstance().removeFromList("followed", "names", name, false));
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
// We shouldn't store or pre-fetch data for this transaction
assertFalse(storageManager.canStoreData(transactionData));