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;
|
||||
}
|
||||
|
||||
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;
|
||||
dirName = directory;
|
||||
fileName = file;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigFileConfigurationAdapter getFileConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public File getFile() {
|
||||
if (file == null) {
|
||||
if (dirName != null) {
|
||||
@ -175,6 +177,7 @@ public class Config extends ConfigSection {
|
||||
* @param autosaveInterval time in seconds
|
||||
* @return this class
|
||||
*/
|
||||
@NotNull
|
||||
public Config setAutosaveInterval(int autosaveInterval) {
|
||||
this.autosaveInterval = autosaveInterval;
|
||||
return this;
|
||||
@ -202,6 +205,7 @@ public class Config extends ConfigSection {
|
||||
/**
|
||||
* Default comment applied to config nodes
|
||||
*/
|
||||
@Nullable
|
||||
public ConfigFormattingRules.CommentStyle getDefaultNodeCommentFormat() {
|
||||
return defaultNodeCommentFormat;
|
||||
}
|
||||
@ -211,7 +215,8 @@ public class Config extends ConfigSection {
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
public Config setDefaultNodeCommentFormat(ConfigFormattingRules.CommentStyle defaultNodeCommentFormat) {
|
||||
@NotNull
|
||||
public Config setDefaultNodeCommentFormat(@Nullable ConfigFormattingRules.CommentStyle defaultNodeCommentFormat) {
|
||||
this.defaultNodeCommentFormat = defaultNodeCommentFormat;
|
||||
return this;
|
||||
}
|
||||
@ -219,6 +224,7 @@ public class Config extends ConfigSection {
|
||||
/**
|
||||
* Default comment applied to section nodes
|
||||
*/
|
||||
@Nullable
|
||||
public ConfigFormattingRules.CommentStyle getDefaultSectionCommentFormat() {
|
||||
return defaultSectionCommentFormat;
|
||||
}
|
||||
@ -228,7 +234,8 @@ public class Config extends ConfigSection {
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
public Config setDefaultSectionCommentFormat(ConfigFormattingRules.CommentStyle defaultSectionCommentFormat) {
|
||||
@NotNull
|
||||
public Config setDefaultSectionCommentFormat(@Nullable ConfigFormattingRules.CommentStyle defaultSectionCommentFormat) {
|
||||
this.defaultSectionCommentFormat = defaultSectionCommentFormat;
|
||||
return this;
|
||||
}
|
||||
@ -245,6 +252,7 @@ public class Config extends ConfigSection {
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
@NotNull
|
||||
public Config setRootNodeSpacing(int rootNodeSpacing) {
|
||||
this.rootNodeSpacing = rootNodeSpacing;
|
||||
return this;
|
||||
@ -264,6 +272,7 @@ public class Config extends ConfigSection {
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
@NotNull
|
||||
public Config setCommentSpacing(int commentSpacing) {
|
||||
this.commentSpacing = commentSpacing;
|
||||
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