mirror of
https://github.com/songoda/SongodaCore.git
synced 2024-11-23 18:45:34 +01:00
add simple file storage for objects
This commit is contained in:
parent
cb4db2bb34
commit
2488e97ba6
@ -126,16 +126,18 @@ public class Config extends ConfigSection {
|
|||||||
fileName = file;
|
fileName = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Config(@NotNull Plugin plugin, @NotNull String directory, @NotNull String file) {
|
public Config(@NotNull Plugin plugin, @Nullable String directory, @NotNull String file) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
dirName = directory;
|
dirName = directory;
|
||||||
fileName = file;
|
fileName = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public ConfigFileConfigurationAdapter getFileConfig() {
|
public ConfigFileConfigurationAdapter getFileConfig() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public File getFile() {
|
public File getFile() {
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
if (dirName != null) {
|
if (dirName != null) {
|
||||||
@ -175,6 +177,7 @@ public class Config extends ConfigSection {
|
|||||||
* @param autosaveInterval time in seconds
|
* @param autosaveInterval time in seconds
|
||||||
* @return this class
|
* @return this class
|
||||||
*/
|
*/
|
||||||
|
@NotNull
|
||||||
public Config setAutosaveInterval(int autosaveInterval) {
|
public Config setAutosaveInterval(int autosaveInterval) {
|
||||||
this.autosaveInterval = autosaveInterval;
|
this.autosaveInterval = autosaveInterval;
|
||||||
return this;
|
return this;
|
||||||
@ -202,6 +205,7 @@ public class Config extends ConfigSection {
|
|||||||
/**
|
/**
|
||||||
* Default comment applied to config nodes
|
* Default comment applied to config nodes
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public ConfigFormattingRules.CommentStyle getDefaultNodeCommentFormat() {
|
public ConfigFormattingRules.CommentStyle getDefaultNodeCommentFormat() {
|
||||||
return defaultNodeCommentFormat;
|
return defaultNodeCommentFormat;
|
||||||
}
|
}
|
||||||
@ -211,7 +215,8 @@ public class Config extends ConfigSection {
|
|||||||
*
|
*
|
||||||
* @return this config
|
* @return this config
|
||||||
*/
|
*/
|
||||||
public Config setDefaultNodeCommentFormat(ConfigFormattingRules.CommentStyle defaultNodeCommentFormat) {
|
@NotNull
|
||||||
|
public Config setDefaultNodeCommentFormat(@Nullable ConfigFormattingRules.CommentStyle defaultNodeCommentFormat) {
|
||||||
this.defaultNodeCommentFormat = defaultNodeCommentFormat;
|
this.defaultNodeCommentFormat = defaultNodeCommentFormat;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -219,6 +224,7 @@ public class Config extends ConfigSection {
|
|||||||
/**
|
/**
|
||||||
* Default comment applied to section nodes
|
* Default comment applied to section nodes
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public ConfigFormattingRules.CommentStyle getDefaultSectionCommentFormat() {
|
public ConfigFormattingRules.CommentStyle getDefaultSectionCommentFormat() {
|
||||||
return defaultSectionCommentFormat;
|
return defaultSectionCommentFormat;
|
||||||
}
|
}
|
||||||
@ -228,7 +234,8 @@ public class Config extends ConfigSection {
|
|||||||
*
|
*
|
||||||
* @return this config
|
* @return this config
|
||||||
*/
|
*/
|
||||||
public Config setDefaultSectionCommentFormat(ConfigFormattingRules.CommentStyle defaultSectionCommentFormat) {
|
@NotNull
|
||||||
|
public Config setDefaultSectionCommentFormat(@Nullable ConfigFormattingRules.CommentStyle defaultSectionCommentFormat) {
|
||||||
this.defaultSectionCommentFormat = defaultSectionCommentFormat;
|
this.defaultSectionCommentFormat = defaultSectionCommentFormat;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -245,6 +252,7 @@ public class Config extends ConfigSection {
|
|||||||
*
|
*
|
||||||
* @return this config
|
* @return this config
|
||||||
*/
|
*/
|
||||||
|
@NotNull
|
||||||
public Config setRootNodeSpacing(int rootNodeSpacing) {
|
public Config setRootNodeSpacing(int rootNodeSpacing) {
|
||||||
this.rootNodeSpacing = rootNodeSpacing;
|
this.rootNodeSpacing = rootNodeSpacing;
|
||||||
return this;
|
return this;
|
||||||
@ -264,6 +272,7 @@ public class Config extends ConfigSection {
|
|||||||
*
|
*
|
||||||
* @return this config
|
* @return this config
|
||||||
*/
|
*/
|
||||||
|
@NotNull
|
||||||
public Config setCommentSpacing(int commentSpacing) {
|
public Config setCommentSpacing(int commentSpacing) {
|
||||||
this.commentSpacing = commentSpacing;
|
this.commentSpacing = commentSpacing;
|
||||||
return this;
|
return this;
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.songoda.core.configuration;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
|
||||||
|
public interface DataStoreObject<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a unique hashable instance of T to store this value under
|
||||||
|
*/
|
||||||
|
public abstract T getKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a unique identifier for saving this value with
|
||||||
|
*/
|
||||||
|
public abstract String getConfigKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save this data to a ConfigurationSection
|
||||||
|
*
|
||||||
|
* @param sec
|
||||||
|
*/
|
||||||
|
public abstract void saveToSection(ConfigurationSection sec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if this data has changed from the state saved to file
|
||||||
|
*/
|
||||||
|
public boolean hasChanged();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this data as needing a save or not
|
||||||
|
*
|
||||||
|
* @param isChanged
|
||||||
|
*/
|
||||||
|
public void setChanged(boolean isChanged);
|
||||||
|
}
|
@ -0,0 +1,220 @@
|
|||||||
|
package com.songoda.core.configuration;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to easily store a set of one data value
|
||||||
|
*
|
||||||
|
* @param <K> Key value that DataObject class uses to uniquely identify values
|
||||||
|
* @param <T> DataObject class that is used to store the data
|
||||||
|
* @since 2019-09-06
|
||||||
|
* @author jascotty2
|
||||||
|
*/
|
||||||
|
public class SimpleDataStore<K, T extends DataStoreObject<K>> {
|
||||||
|
|
||||||
|
protected final Plugin plugin;
|
||||||
|
protected final String filename, dirName;
|
||||||
|
private final Function<ConfigurationSection, T> getFromSection;
|
||||||
|
protected final HashMap<K, T> data = new HashMap();
|
||||||
|
private File file;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
SaveTask saveTask;
|
||||||
|
Timer autosaveTimer;
|
||||||
|
/**
|
||||||
|
* time in seconds to start a save after a change is made
|
||||||
|
*/
|
||||||
|
int autosaveInterval = 60;
|
||||||
|
|
||||||
|
public SimpleDataStore(@NotNull Plugin plugin, @NotNull String filename, Function<ConfigurationSection, T> loadFunction) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.filename = filename;
|
||||||
|
dirName = null;
|
||||||
|
this.getFromSection = loadFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleDataStore(@NotNull Plugin plugin, @Nullable String directory, @NotNull String filename, @NotNull Function<ConfigurationSection, T> loadFunction) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.filename = filename;
|
||||||
|
this.dirName = directory;
|
||||||
|
this.getFromSection = loadFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public File getFile() {
|
||||||
|
if (file == null) {
|
||||||
|
if (dirName != null) {
|
||||||
|
this.file = new File(plugin.getDataFolder() + dirName, filename != null ? filename : "data.yml");
|
||||||
|
} else {
|
||||||
|
this.file = new File(plugin.getDataFolder(), filename != null ? filename : "data.yml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a directly-modifiable instance of the data mapping for this storage
|
||||||
|
*/
|
||||||
|
public Map<K, T> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value to which the specified key is mapped,
|
||||||
|
* or {@code null} if this map contains no mapping for the key.
|
||||||
|
*
|
||||||
|
* @param key key whose mapping is to be retrieved from this storage
|
||||||
|
* @return the value associated with <tt>key</tt>, or
|
||||||
|
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public T get(K key) {
|
||||||
|
return data.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the mapping for the specified key from this storage if present.
|
||||||
|
*
|
||||||
|
* @param key key whose mapping is to be removed from this storage
|
||||||
|
* @return the previous value associated with <tt>key</tt>, or
|
||||||
|
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public T remove(@NotNull K key) {
|
||||||
|
T temp;
|
||||||
|
synchronized (lock) {
|
||||||
|
temp = data.remove(key);
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the mapping for the specified key from this storage if present.
|
||||||
|
*
|
||||||
|
* @param value value whose mapping is to be removed from this storage
|
||||||
|
* @return the previous value associated with <tt>key</tt>, or
|
||||||
|
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public T remove(@NotNull T value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
T temp;
|
||||||
|
synchronized (lock) {
|
||||||
|
temp = data.remove(value.getKey());
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the specified value in this storage. If the map previously contained
|
||||||
|
* a mapping for the key, the old value is replaced.
|
||||||
|
*
|
||||||
|
* @param value value to be added
|
||||||
|
* @return the previous value associated with <tt>value.getKey()</tt>, or
|
||||||
|
* <tt>null</tt> if there was no mapping for <tt>value.getKey()</tt>.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public T add(@NotNull T value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
T temp;
|
||||||
|
synchronized (lock) {
|
||||||
|
temp = data.put(value.getKey(), value);
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load data from the associated file
|
||||||
|
*/
|
||||||
|
public void load() {
|
||||||
|
if (!getFile().exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
YamlConfiguration f = new YamlConfiguration();
|
||||||
|
f.options().pathSeparator('\0');
|
||||||
|
f.load(file);
|
||||||
|
|
||||||
|
synchronized (lock) {
|
||||||
|
data.clear();
|
||||||
|
f.getValues(false).entrySet().stream()
|
||||||
|
.filter(d -> d.getValue() instanceof ConfigurationSection)
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.map(v -> getFromSection.apply((ConfigurationSection) v))
|
||||||
|
.forEach(v -> data.put(v.getKey(), v));
|
||||||
|
}
|
||||||
|
} catch (IOException | InvalidConfigurationException ex) {
|
||||||
|
plugin.getLogger().log(Level.SEVERE, "Failed to load data from " + file.getName(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally save this storage's data to file if there have been changes made
|
||||||
|
*/
|
||||||
|
public void saveChanges() {
|
||||||
|
if (saveTask != null || data.values().stream().anyMatch(v -> v.hasChanged())) {
|
||||||
|
flushSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save this file data. This saves later asynchronously.
|
||||||
|
*/
|
||||||
|
public void save() {
|
||||||
|
// save async even if no plugin or if plugin disabled
|
||||||
|
if (saveTask == null) {
|
||||||
|
autosaveTimer = new Timer((plugin != null ? plugin.getName() + "-DataStoreSave-" : "DataStoreSave-") + getFile().getName());
|
||||||
|
autosaveTimer.schedule(saveTask = new SaveTask(), autosaveInterval * 1000L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force a new save of this storage's data
|
||||||
|
*/
|
||||||
|
public void flushSave() {
|
||||||
|
if (saveTask != null) {
|
||||||
|
//Close Threads
|
||||||
|
saveTask.cancel();
|
||||||
|
autosaveTimer.cancel();
|
||||||
|
saveTask = null;
|
||||||
|
autosaveTimer = null;
|
||||||
|
}
|
||||||
|
YamlConfiguration f = new YamlConfiguration();
|
||||||
|
synchronized (lock) {
|
||||||
|
data.values().stream().forEach(e -> e.saveToSection(f.createSection(e.getConfigKey())));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
f.save(getFile());
|
||||||
|
data.values().stream().forEach(e -> e.setChanged(false));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
plugin.getLogger().log(Level.SEVERE, "Failed to save data to " + file.getName(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SaveTask extends TimerTask {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
flushSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user