Basic functioning plugin completed.

This commit is contained in:
2024-11-11 08:08:12 +00:00
parent fd1961d5f3
commit 4bf7d5f82f
25 changed files with 970 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
package solutions.reslate.entertainment.spigotresourcesync;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Collection;
import java.util.HashSet;
import java.util.logging.Logger;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import solutions.reslate.entertainment.spigotresourcesync.data.Configuration;
import solutions.reslate.entertainment.spigotresourcesync.events.ObjectLoadListener;
import solutions.reslate.entertainment.spigotresourcesync.serialisation.Serialiser;
public class ConfigManager extends FileConfiguration {
private Serialiser<Configuration> serialiser;
private Configuration config;
private File file;
private Logger logger;
private Collection<ObjectLoadListener<Configuration>> configLoadListener;
public ConfigManager(File file, Serialiser<Configuration> serialiser, Logger logger) {
super();
this.serialiser = serialiser;
this.config = new Configuration();
this.file = file;
this.logger = logger;
this.configLoadListener = new HashSet<>();
}
public void flush() {
try {
save(this.file);
} catch (IOException e) {
logger.severe(String.format("Unable to save configuration to \"%s\". %s", this.file.getAbsolutePath(), e.getMessage()));
}
}
@Override
public void save(String file) throws IOException {
this.save(new File(file));
}
@Override
public void save(File file) throws IOException {
this.serialiser.flushTo(file, this.config);
logger.info(String.format("Saved configuration to \"%s\".", this.file.getAbsolutePath()));
}
public void load() {
try {
this.load(file);
} catch (IOException | InvalidConfigurationException e) {
logger.severe(String.format("Unable to load configuration from \"%s\".", file.getAbsolutePath(), e.getMessage()));
if (file.exists()) {
logger.severe(String.format("Found pre-existing file. No overwriting will occur. Delete \"%s\" and restart to generate new configuration.", this.file.getAbsolutePath()));
} else {
flush();
}
}
}
@Override
public void load(String file) throws FileNotFoundException, IOException, InvalidConfigurationException {
this.load(new File(file));
}
@Override
public void load(File file) throws FileNotFoundException, IOException, InvalidConfigurationException {
try (FileReader reader = new FileReader(file)) {
this.load(reader);
}
}
@Override
public void load(Reader reader) throws IOException, InvalidConfigurationException {
this.config = this.serialiser.load(reader, Configuration.class);
logger.info(String.format("Successfully loaded configuration from \"%s\".", this.file.getAbsolutePath()));
onLoadConfig();
}
public Configuration getConfiguration() {
return config;
}
private void onLoadConfig() {
for (ObjectLoadListener<Configuration> objectUpdateListener : configLoadListener) {
objectUpdateListener.objectLoaded(config);
}
}
@Override
public String saveToString() {
return serialiser.serialize(this.config);
}
@Override
public void loadFromString(String contents) throws InvalidConfigurationException {
this.config = serialiser.deserialize(contents, Configuration.class);
onLoadConfig();
}
public void addConfigLoadListener(ObjectLoadListener<Configuration> listener) {
this.configLoadListener.add(listener);
}
public void removeConfigLoadListener(ObjectLoadListener<Configuration> listener) {
this.configLoadListener.remove(listener);
}
public void resetConfiguration() {
this.config = new Configuration();
}
}

View File

@@ -0,0 +1,54 @@
package solutions.reslate.entertainment.spigotresourcesync;
import java.io.File;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import solutions.reslate.entertainment.spigotresourcesync.serialisation.JacksonYamlSerialiser;
import solutions.reslate.entertainment.spigotresourcesync.synchronisation.ApacheCommonsIOSynchroniser;
public class SpigotResourceSync extends JavaPlugin {
private ConfigManager configManager;
private SyncListManager syncListManager;
@Override
public void onDisable() {
configManager.flush();
super.onDisable();
}
@Override
public void onLoad() {
configManager = new ConfigManager(new File("plugins", getName() + ".yml"), new JacksonYamlSerialiser<>(), getLogger());
configManager.load();
syncListManager = new SyncListManager(configManager.getConfiguration(), new ApacheCommonsIOSynchroniser(), getLogger());
configManager.addConfigLoadListener(syncListManager);
if (configManager.getConfiguration().getSyncOnLoad()) {
syncListManager.synchroniseAllSyncList();
}
super.onLoad();
}
@Override
public void onEnable() {
super.onEnable();
}
@Override
public FileConfiguration getConfig() {
return configManager;
}
@Override
public void saveConfig() {
configManager.flush();
}
@Override
public void saveDefaultConfig() {
configManager.resetConfiguration();
configManager.flush();
super.saveDefaultConfig();
}
}

View File

@@ -0,0 +1,47 @@
package solutions.reslate.entertainment.spigotresourcesync;
import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
import solutions.reslate.entertainment.spigotresourcesync.data.Configuration;
import solutions.reslate.entertainment.spigotresourcesync.data.SyncPair;
import solutions.reslate.entertainment.spigotresourcesync.events.ObjectLoadListener;
import solutions.reslate.entertainment.spigotresourcesync.synchronisation.Synchroniser;
public class SyncListManager implements ObjectLoadListener<Configuration> {
private Configuration configuration;
private Synchroniser synchroniser;
private Logger logger;
public SyncListManager(Configuration configuration, Synchroniser synchroniser, Logger logger) {
super();
this.logger = logger;
this.configuration = configuration;
this.synchroniser = synchroniser;
}
public void synchroniseAllSyncList() {
logger.info("Synchronising all sync pairs...");
for (SyncPair syncPair : this.configuration.getSyncList().gatherAllSyncPairs()) {
File source = new File(syncPair.getSource());
File dest = new File(syncPair.getDestination());
try {
synchroniser.sync(source, dest);
logger.info(String.format("Synchronised \"%s\" to \"%s\"!", source.getName(), dest.getName()));
} catch (IOException e) {
logger.warning(String.format("Failed to synchronise \"%s\" to \"%s\". %s", source.getAbsolutePath(), dest.getAbsolutePath(), e.getMessage()));
}
}
logger.info("Done synchronising.");
}
@Override
public void objectLoaded(Configuration obj) {
logger.info("Updating synchronisation list due to recently loading configuration...");
this.configuration = obj;
synchroniseAllSyncList();
logger.info("Done.");
}
}

View File

@@ -0,0 +1,42 @@
package solutions.reslate.entertainment.spigotresourcesync.data;
import java.io.Serializable;
public class Configuration implements Serializable {
private boolean enabled;
private boolean syncOnLoad;
private SyncList syncList;
public Configuration() {
super();
enabled = true;
syncOnLoad = true;
syncList = new SyncList();
syncList.addSynchronisationPair("exampleA", "exampleB", "general");
}
public boolean getEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean getSyncOnLoad() {
return syncOnLoad;
}
public void setSyncOnLoad(boolean syncOnLoad) {
this.syncOnLoad = syncOnLoad;
}
public SyncList getSyncList() {
return syncList;
}
public void setSyncList(SyncList syncList) {
this.syncList = syncList;
}
}

View File

@@ -0,0 +1,67 @@
package solutions.reslate.entertainment.spigotresourcesync.data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.SequencedCollection;
import java.util.SequencedMap;
public class SyncList implements Serializable {
private SequencedMap<String, SequencedCollection<SyncPair>> syncPairs;
public SyncList() {
super();
this.syncPairs = new LinkedHashMap<>();
}
public List<SyncPair> gatherAllSyncPairs() {
List<SyncPair> allSyncPairs = new ArrayList<>();
for (SequencedCollection<SyncPair> syncPairs : this.syncPairs.values()) {
allSyncPairs.addAll(syncPairs);
}
return allSyncPairs;
}
public void addSynchronisationPair(String source, String dest, String group) {
this.addSynchronisationPair(new SyncPair(source, dest), group);
}
public void addSynchronisationPair(SyncPair syncPair, String group) {
addSynchronisationGroup(group);
this.syncPairs.get(group).add(syncPair);
}
public void removeSynchronisationSet(String source, String dest, String group) {
if (this.syncPairs.containsKey(group)) {
this.syncPairs.get(group).remove(new SyncPair(source, dest));
}
}
public void addSynchronisationGroup(String group) {
if (!this.syncPairs.containsKey(group)) {
this.syncPairs.put(group, new LinkedHashSet<>());
}
}
public void removeSynchronisationGroup(String group) {
if (this.syncPairs.containsKey(group)) {
this.syncPairs.remove(group);
}
}
public List<String> gatherSynchronisationGroups() {
return new ArrayList<>(this.syncPairs.keySet());
}
public SequencedMap<String, SequencedCollection<SyncPair>> getSyncPairs() {
return syncPairs;
}
public void setSyncPairs(SequencedMap<String, SequencedCollection<SyncPair>> syncPairs) {
this.syncPairs = syncPairs;
}
}

View File

@@ -0,0 +1,55 @@
package solutions.reslate.entertainment.spigotresourcesync.data;
import java.io.Serializable;
import java.util.Objects;
public class SyncPair implements Serializable {
private String source;
private String destination;
public SyncPair(String source, String destination) {
super();
this.source = source;
this.destination = destination;
}
public SyncPair() {
super();
}
public void setSource(String source) {
this.source = source;
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getSource() {
return source;
}
public String getDestination() {
return destination;
}
@Override
public int hashCode() {
return Objects.hash(this.source, this.destination);
}
@Override
public boolean equals(Object obj) {
if (getClass() != obj.getClass()) {
return false;
}
SyncPair syncPair = (SyncPair) obj;
return this.source.equals(syncPair.source)
&& this.destination.equals(syncPair.destination);
}
@Override
public String toString() {
return String.format("(%s, %s)", this.source, this.destination);
}
}

View File

@@ -0,0 +1,7 @@
package solutions.reslate.entertainment.spigotresourcesync.events;
import java.util.EventListener;
public interface ObjectLoadListener<T> extends EventListener {
void objectLoaded(T obj);
}

View File

@@ -0,0 +1,68 @@
package solutions.reslate.entertainment.spigotresourcesync.serialisation;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature;
public class JacksonYamlSerialiser<T> implements Serialiser<T> {
private ObjectMapper mapper;
public JacksonYamlSerialiser() {
super();
YAMLFactory yamlFactory = new YAMLFactory()
.disable(Feature.WRITE_DOC_START_MARKER);
mapper = new ObjectMapper(yamlFactory)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
@Override
public void flushTo(File file, T serialisable) throws IOException {
try (FileWriter writer = new FileWriter(file)) {
mapper.writeValue(writer, serialisable);
}
}
@Override
public T load(File file, Class<T> type) throws IOException {
try (FileReader reader = new FileReader(file)) {
return this.load(reader, type);
}
}
@Override
public T load(Reader reader, Class<T> type) throws IOException {
try {
return mapper.readValue(reader, type);
} catch (IOException e) {
throw e;
}
}
@Override
public String serialize(T serialisable) {
try {
return mapper.writeValueAsString(serialisable);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Given type cannot be serialised.", e);
}
}
@Override
public T deserialize(String data, Class<T> type) {
try {
return mapper.readValue(data, type);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Given string cannot be deserialised.", e);
}
}
}

View File

@@ -0,0 +1,14 @@
package solutions.reslate.entertainment.spigotresourcesync.serialisation;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
public interface Serialiser<T> {
void flushTo(File file, T serialisable) throws IOException;
String serialize(T serialisable);
T load(File file, Class<T> type) throws IOException;
T load(Reader reader, Class<T> type) throws IOException;
T deserialize(String data, Class<T> type);
}

View File

@@ -0,0 +1,49 @@
package solutions.reslate.entertainment.spigotresourcesync.synchronisation;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import org.apache.commons.io.FileUtils;
public class ApacheCommonsIOSynchroniser implements Synchroniser {
@Override
public void sync(File source, File dest, File root) throws IOException {
if (root != null) {
source = Path.of(root.getPath(), source.getPath()).toFile();
source = Path.of(root.getPath(), dest.getPath()).toFile();
}
if (source.isDirectory()) {
if (dest.isDirectory()) {
FileUtils.copyDirectory(source, dest);
} else {
throw new IOException("Cannot synchronise directy to non-directory!");
// TODO add more detail to error message.
}
} else {
if (dest.isDirectory()) {
FileUtils.copyFileToDirectory(source, dest);
} else {
FileUtils.copyFile(source, dest);
}
}
}
@Override
public void sync(String source, String dest, String root) throws IOException {
this.sync(new File(source), new File(dest), root != null ? new File(root) : null);
}
@Override
public void sync(File source, File dest) throws IOException {
sync(source, dest, null);
}
@Override
public void sync(String source, String dest) throws IOException {
sync(source, dest, null);
}
}

View File

@@ -0,0 +1,11 @@
package solutions.reslate.entertainment.spigotresourcesync.synchronisation;
import java.io.File;
import java.io.IOException;
public interface Synchroniser {
void sync(File source, File dest, File root) throws IOException;
void sync(File source, File dest) throws IOException;
void sync(String source, String dest, String root) throws IOException;
void sync(String source, String dest) throws IOException;
}

View File

@@ -0,0 +1,3 @@
name: SpigotResourceSync
version: 1.0.0
main: solutions.reslate.entertainment.spigotresourcesync.SpigotResourceSync

View File

@@ -0,0 +1,32 @@
package solutions.reslate.entertainment.spigotresourcesync;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
import org.junit.jupiter.api.Test;
import solutions.reslate.entertainment.spigotresourcesync.data.Configuration;
import solutions.reslate.entertainment.spigotresourcesync.data.SyncPair;
import solutions.reslate.entertainment.spigotresourcesync.serialisation.JacksonYamlSerialiser;
import solutions.reslate.entertainment.spigotresourcesync.serialisation.Serialiser;
public class TestConfigManager {
@Test
void persistConfiguration() throws IOException {
File dummyConfig = File.createTempFile("dummy_config", ".yml");
Serialiser<Configuration> dummySerialiser = new JacksonYamlSerialiser<>();
Logger logger = Logger.getLogger(this.getClass().getName());
ConfigManager initialConfigManager = new ConfigManager(dummyConfig, dummySerialiser, logger);
SyncPair dummySyncPair = new SyncPair("abc", "def");
initialConfigManager.getConfiguration().getSyncList().addSynchronisationPair(dummySyncPair, "a");
initialConfigManager.flush();
assumeTrue(dummyConfig.exists());
ConfigManager finalConfigManager = new ConfigManager(dummyConfig, dummySerialiser, logger);
finalConfigManager.load();
assertEquals(dummySyncPair, finalConfigManager.getConfiguration().getSyncList().gatherAllSyncPairs().get(0));
}
}

View File

@@ -0,0 +1,10 @@
package solutions.reslate.entertainment.spigotresourcesync;
import org.junit.jupiter.api.Test;
public class TestSyncListManager {
@Test
void testDirectorySync() {
// TODO Write directory sync test.
}
}

View File

@@ -0,0 +1,14 @@
package solutions.reslate.entertainment.spigotresourcesync.data;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class TestSynchronisationPair {
@Test
void dataEqualsThereforeObjectEquals() {
SyncPair firstPair = new SyncPair("abc", "def");
SyncPair secondPair = new SyncPair("abc", "def");
assertEquals(firstPair, secondPair);
}
}

View File

@@ -0,0 +1,78 @@
package solutions.reslate.entertainment.spigotresourcesync.serialisation;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.SequencedCollection;
import java.util.SequencedMap;
import org.junit.jupiter.api.Test;
import solutions.reslate.entertainment.spigotresourcesync.data.SyncPair;
public class TestJacksonYamlSerialiser {
@Test
void testListSerialisation() throws IOException {
ArrayList<String> dummyList = new ArrayList<>();
dummyList.add("abc");
dummyList.add("def");
File dummyFile = File.createTempFile("dummy_file", ".yml");
JacksonYamlSerialiser<List<String>> dummyJacksonYamlSerialiser = new JacksonYamlSerialiser<>();
dummyJacksonYamlSerialiser.flushTo(dummyFile, dummyList);
assumeTrue(dummyFile.exists());
List<String> writtenLines = Files.readAllLines(dummyFile.toPath());
assertEquals(2, writtenLines.size());
assertEquals("- \"abc\"", writtenLines.get(0));
assertEquals("- \"def\"", writtenLines.get(1));
}
@Test
void testSyncPairSerialisation() throws IOException {
File dummyFile = File.createTempFile("dummy_file", ".yml");
SyncPair dummySyncPair = new SyncPair("abc", "def");
JacksonYamlSerialiser<SyncPair> dummyJacksonYamlSerialiser = new JacksonYamlSerialiser<>();
dummyJacksonYamlSerialiser.flushTo(dummyFile, dummySyncPair);
assumeTrue(dummyFile.exists());
List<String> writtenLines = Files.readAllLines(dummyFile.toPath());
assertEquals(2, writtenLines.size());
assertEquals("source: \"abc\"", writtenLines.get(0));
assertEquals("destination: \"def\"", writtenLines.get(1));
}
@Test
void testSequencedCollectionSerialisation() throws IOException {
File dummyFile = File.createTempFile("dummy_file", ".yml");
SequencedCollection<String> collection = new LinkedHashSet<>();
collection.add("abc");
collection.add("def");
JacksonYamlSerialiser<SequencedCollection<String>> dummyJacksonYamlSerialiser = new JacksonYamlSerialiser<>();
dummyJacksonYamlSerialiser.flushTo(dummyFile, collection);
assumeTrue(dummyFile.exists());
List<String> writtenLines = Files.readAllLines(dummyFile.toPath());
assertEquals(2, writtenLines.size());
assertEquals("- \"abc\"", writtenLines.get(0));
assertEquals("- \"def\"", writtenLines.get(1));
}
@Test
void testSequencedMapSerialisation() throws IOException {
File dummyFile = File.createTempFile("dummy_file", ".yml");
SequencedMap<String, String> collection = new LinkedHashMap<>();
collection.put("abc", "def");
collection.put("123", "456");
JacksonYamlSerialiser<SequencedMap<String, String>> dummyJacksonYamlSerialiser = new JacksonYamlSerialiser<>();
dummyJacksonYamlSerialiser.flushTo(dummyFile, collection);
assumeTrue(dummyFile.exists());
List<String> writtenLines = Files.readAllLines(dummyFile.toPath());
assertEquals(2, writtenLines.size());
assertEquals("abc: \"def\"", writtenLines.get(0));
assertEquals("\"123\": \"456\"", writtenLines.get(1));
}
}

View File

@@ -0,0 +1,44 @@
package solutions.reslate.entertainment.spigotresourcesync.synchroniser;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.junit.jupiter.api.Test;
import solutions.reslate.entertainment.spigotresourcesync.synchronisation.ApacheCommonsIOSynchroniser;
public class TestApacheCommonsIOSynchroniser {
@Test
void testDir2DirSync() throws IOException {
ApacheCommonsIOSynchroniser dummySynchroniser = new ApacheCommonsIOSynchroniser();
File dummyDirA = Files.createTempDirectory("dir_A").toFile();
File dummyDirB = Files.createTempDirectory("dir_B").toFile();
File dummyFileA = File.createTempFile("file_A", ".txt", dummyDirA);
dummySynchroniser.sync(dummyDirA, dummyDirB);
assertTrue(new File(dummyDirB, dummyFileA.getName()).exists());
}
@Test
void testFile2DirSync() throws IOException {
ApacheCommonsIOSynchroniser dummySynchroniser = new ApacheCommonsIOSynchroniser();
File dummyDirA = Files.createTempDirectory("dir_A").toFile();
File dummyDirB = Files.createTempDirectory("dir_B").toFile();
File dummyFileA = File.createTempFile("file_A", ".txt", dummyDirA);
dummySynchroniser.sync(dummyFileA, dummyDirB);
assertTrue(new File(dummyDirB, dummyFileA.getName()).exists());
}
@Test
void testFile2FileSync() throws IOException {
ApacheCommonsIOSynchroniser dummySynchroniser = new ApacheCommonsIOSynchroniser();
File dummyDirA = Files.createTempDirectory("dir_A").toFile();
File dummyDirB = Files.createTempDirectory("dir_B").toFile();
File dummyFileA = File.createTempFile("file_A", ".txt", dummyDirA);
File dummyFileB = new File(dummyDirB, "file_B");
dummySynchroniser.sync(dummyFileA, dummyFileB);
assertTrue(dummyFileB.exists());
}
}