From 35def54eccf5d0d8b2d866450c3d61c0312ac360 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 2 Apr 2023 14:42:49 +0100 Subject: [PATCH] Added support for multiple block/follow lists. Any list with the following prefix will be used in block/follow logic: blockedNames blockedAddresses followedNames For instance, any names in a list named "blockedNames_CustomBlockList" would also be blocked, along with those in the standard "blockedNames" list. This will ultimately allow apps to offer custom block/follow lists to users (once list functionality is added to the Q-Apps API). --- .../java/org/qortal/list/ResourceList.java | 16 +++ .../org/qortal/list/ResourceListManager.java | 67 +++++++++ src/main/java/org/qortal/utils/ListUtils.java | 10 +- src/test/java/org/qortal/test/ListTests.java | 129 ++++++++++++++++++ 4 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 src/test/java/org/qortal/test/ListTests.java diff --git a/src/main/java/org/qortal/list/ResourceList.java b/src/main/java/org/qortal/list/ResourceList.java index 099aa168..5c12e0f5 100644 --- a/src/main/java/org/qortal/list/ResourceList.java +++ b/src/main/java/org/qortal/list/ResourceList.java @@ -52,6 +52,15 @@ public class ResourceList { String jsonString = ResourceList.listToJSONString(this.list); Path filePath = this.getFilePath(); + // Don't create list if it's empty + if (this.list != null && this.list.isEmpty()) { + if (filePath != null && Files.exists(filePath)) { + // Delete empty list + Files.delete(filePath); + } + return; + } + // Create parent directory if needed try { Files.createDirectories(filePath.getParent()); @@ -109,6 +118,13 @@ public class ResourceList { this.list.remove(resource); } + public void clear() { + if (this.list == null) { + return; + } + this.list.clear(); + } + public boolean contains(String resource, boolean caseSensitive) { if (resource == null || this.list == null) { return false; diff --git a/src/main/java/org/qortal/list/ResourceListManager.java b/src/main/java/org/qortal/list/ResourceListManager.java index 4182f87c..f694ebd5 100644 --- a/src/main/java/org/qortal/list/ResourceListManager.java +++ b/src/main/java/org/qortal/list/ResourceListManager.java @@ -2,8 +2,11 @@ package org.qortal.list; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.qortal.settings.Settings; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -18,6 +21,7 @@ public class ResourceListManager { public ResourceListManager() { + this.lists = this.fetchLists(); } public static synchronized ResourceListManager getInstance() { @@ -27,6 +31,38 @@ public class ResourceListManager { return instance; } + public static synchronized void reset() { + if (instance != null) { + instance = null; + } + } + + private List fetchLists() { + List lists = new ArrayList<>(); + Path listsPath = Paths.get(Settings.getInstance().getListsPath()); + + if (listsPath.toFile().isDirectory()) { + String[] files = listsPath.toFile().list(); + + for (String fileName : files) { + try { + // Remove .json extension + if (fileName.endsWith(".json")) { + fileName = fileName.substring(0, fileName.length() - 5); + } + + ResourceList list = new ResourceList(fileName); + if (list != null) { + lists.add(list); + } + } catch (IOException e) { + // Ignore this list + } + } + } + return lists; + } + private ResourceList getList(String listName) { for (ResourceList list : this.lists) { if (Objects.equals(list.getName(), listName)) { @@ -48,6 +84,18 @@ public class ResourceListManager { } + private List getListsByPrefix(String listNamePrefix) { + List lists = new ArrayList<>(); + + for (ResourceList list : this.lists) { + if (list != null && list.getName() != null && list.getName().startsWith(listNamePrefix)) { + lists.add(list); + } + } + + return lists; + } + public boolean addToList(String listName, String item, boolean save) { ResourceList list = this.getList(listName); if (list == null) { @@ -95,6 +143,16 @@ public class ResourceListManager { return list.contains(item, caseSensitive); } + public boolean listWithPrefixContains(String listNamePrefix, String item, boolean caseSensitive) { + List lists = getListsByPrefix(listNamePrefix); + for (ResourceList list : lists) { + if (list.contains(item, caseSensitive)) { + return true; + } + } + return false; + } + public void saveList(String listName) { ResourceList list = this.getList(listName); if (list == null) { @@ -133,6 +191,15 @@ public class ResourceListManager { return list.getList(); } + public List getStringsInListsWithPrefix(String listNamePrefix) { + List items = new ArrayList<>(); + List lists = getListsByPrefix(listNamePrefix); + for (ResourceList list : lists) { + items.addAll(list.getList()); + } + return items; + } + public int getItemCountForList(String listName) { ResourceList list = this.getList(listName); if (list == null) { diff --git a/src/main/java/org/qortal/utils/ListUtils.java b/src/main/java/org/qortal/utils/ListUtils.java index 80aa9bfd..73c847d2 100644 --- a/src/main/java/org/qortal/utils/ListUtils.java +++ b/src/main/java/org/qortal/utils/ListUtils.java @@ -9,26 +9,26 @@ public class ListUtils { /* Blocking */ public static List blockedNames() { - return ResourceListManager.getInstance().getStringsInList("blockedNames"); + return ResourceListManager.getInstance().getStringsInListsWithPrefix("blockedNames"); } public static boolean isNameBlocked(String name) { - return ResourceListManager.getInstance().listContains("blockedNames", name, false); + return ResourceListManager.getInstance().listWithPrefixContains("blockedNames", name, false); } public static boolean isAddressBlocked(String address) { - return ResourceListManager.getInstance().listContains("blockedAddresses", address, true); + return ResourceListManager.getInstance().listWithPrefixContains("blockedAddresses", address, true); } /* Following */ public static List followedNames() { - return ResourceListManager.getInstance().getStringsInList("followedNames"); + return ResourceListManager.getInstance().getStringsInListsWithPrefix("followedNames"); } public static boolean isFollowingName(String name) { - return ResourceListManager.getInstance().listContains("followedNames", name, false); + return ResourceListManager.getInstance().listWithPrefixContains("followedNames", name, false); } public static int followedNamesCount() { diff --git a/src/test/java/org/qortal/test/ListTests.java b/src/test/java/org/qortal/test/ListTests.java new file mode 100644 index 00000000..75ff8d36 --- /dev/null +++ b/src/test/java/org/qortal/test/ListTests.java @@ -0,0 +1,129 @@ +package org.qortal.test; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.qortal.list.ResourceList; +import org.qortal.list.ResourceListManager; +import org.qortal.repository.DataException; +import org.qortal.settings.Settings; +import org.qortal.test.common.Common; +import org.qortal.utils.ListUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; + +import static org.junit.Assert.*; + +public class ListTests { + + @Before + public void beforeTest() throws DataException, IOException { + Common.useDefaultSettings(); + this.cleanup(); + } + + @After + public void afterTest() throws DataException, IOException { + this.cleanup(); + } + + private void cleanup() throws IOException { + // Delete custom lists created by test methods + ResourceList followedNamesTestList = new ResourceList("followedNames_test"); + followedNamesTestList.clear(); + followedNamesTestList.save(); + + ResourceList blockedNamesTestList = new ResourceList("blockedNames_test"); + blockedNamesTestList.clear(); + blockedNamesTestList.save(); + + // Clear resource list manager instance + ResourceListManager.reset(); + } + + @Test + public void testSingleList() { + ResourceListManager resourceListManager = ResourceListManager.getInstance(); + String listName = "followedNames_test"; + String name = "testName"; + + resourceListManager.addToList(listName, name, false); + + List followedNames = resourceListManager.getStringsInList(listName); + assertEquals(1, followedNames.size()); + assertEquals(followedNames.size(), ListUtils.followedNamesCount()); + assertEquals(name, followedNames.get(0)); + } + + @Test + public void testListPrefix() { + ResourceListManager resourceListManager = ResourceListManager.getInstance(); + + List initialFollowedNames = resourceListManager.getStringsInListsWithPrefix("followedNames"); + assertEquals(0, initialFollowedNames.size()); + + List initialBlockedNames = resourceListManager.getStringsInListsWithPrefix("blockedNames"); + assertEquals(0, initialBlockedNames.size()); + + // Add to multiple lists + resourceListManager.addToList("followedNames_CustomList1", "testName1", false); + resourceListManager.addToList("followedNames_CustomList1", "testName2", false); + resourceListManager.addToList("followedNames_CustomList2", "testName3", false); + resourceListManager.addToList("followedNames_CustomList3", "testName4", false); + resourceListManager.addToList("blockedNames_CustomList1", "testName5", false); + + // Check followedNames + List followedNames = resourceListManager.getStringsInListsWithPrefix("followedNames"); + assertEquals(4, followedNames.size()); + assertEquals(followedNames.size(), ListUtils.followedNamesCount()); + assertTrue(followedNames.contains("testName1")); + assertTrue(followedNames.contains("testName2")); + assertTrue(followedNames.contains("testName3")); + assertTrue(followedNames.contains("testName4")); + assertFalse(followedNames.contains("testName5")); + + // Check blockedNames + List blockedNames = resourceListManager.getStringsInListsWithPrefix("blockedNames"); + assertEquals(1, blockedNames.size()); + assertEquals(blockedNames.size(), ListUtils.blockedNames().size()); + assertTrue(blockedNames.contains("testName5")); + } + + @Test + public void testDataPersistence() { + // Ensure lists are empty to begin with + assertEquals(0, ResourceListManager.getInstance().getStringsInListsWithPrefix("followedNames").size()); + assertEquals(0, ResourceListManager.getInstance().getStringsInListsWithPrefix("blockedNames").size()); + + // Add some items to multiple lists + ResourceListManager.getInstance().addToList("followedNames_test", "testName1", true); + ResourceListManager.getInstance().addToList("followedNames_test", "testName2", true); + ResourceListManager.getInstance().addToList("blockedNames_test", "testName3", true); + + // Ensure they are added + assertEquals(2, ResourceListManager.getInstance().getStringsInListsWithPrefix("followedNames").size()); + assertEquals(1, ResourceListManager.getInstance().getStringsInListsWithPrefix("blockedNames").size()); + + // Clear local state + ResourceListManager.reset(); + + // Ensure items are automatically loaded back in from disk + assertEquals(2, ResourceListManager.getInstance().getStringsInListsWithPrefix("followedNames").size()); + assertEquals(1, ResourceListManager.getInstance().getStringsInListsWithPrefix("blockedNames").size()); + + // Delete followedNames file + File followedNamesFile = Paths.get(Settings.getInstance().getListsPath(), "followedNames_test.json").toFile(); + followedNamesFile.delete(); + + // Clear local state again + ResourceListManager.reset(); + + // Ensure only the blocked names are loaded back in + assertEquals(0, ResourceListManager.getInstance().getStringsInListsWithPrefix("followedNames").size()); + assertEquals(1, ResourceListManager.getInstance().getStringsInListsWithPrefix("blockedNames").size()); + } + +}