diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml
index 655b3255..80a1483f 100644
--- a/bukkit/src/main/resources/config.yml
+++ b/bukkit/src/main/resources/config.yml
@@ -43,7 +43,7 @@ apply-shorthand: true
log-notify: true
# Which storage method the plugin should use.
-# Currently supported: mysql, sqlite, h2, flatfile, mongodb
+# Currently supported: mysql, sqlite, h2, json, yaml, mongodb
# Fill out connection info below if you're using MySQL or MongoDB
storage-method: h2
diff --git a/bungee/src/main/resources/config.yml b/bungee/src/main/resources/config.yml
index fa69ea7b..83e11722 100644
--- a/bungee/src/main/resources/config.yml
+++ b/bungee/src/main/resources/config.yml
@@ -43,7 +43,7 @@ apply-shorthand: true
log-notify: true
# Which storage method the plugin should use.
-# Currently supported: mysql, sqlite, h2, flatfile, mongodb
+# Currently supported: mysql, sqlite, h2, json, yaml, mongodb
# Fill out connection info below if you're using MySQL or MongoDB
storage-method: h2
diff --git a/common/pom.xml b/common/pom.xml
index 2c8e8fbe..0f227538 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -67,6 +67,13 @@
1.7.9
compile
+
+
+ org.yaml
+ snakeyaml
+ 1.14
+ provided
+
com.google.code.gson
diff --git a/common/src/main/java/me/lucko/luckperms/storage/StorageFactory.java b/common/src/main/java/me/lucko/luckperms/storage/StorageFactory.java
index d0223675..4ee73918 100644
--- a/common/src/main/java/me/lucko/luckperms/storage/StorageFactory.java
+++ b/common/src/main/java/me/lucko/luckperms/storage/StorageFactory.java
@@ -35,7 +35,7 @@ import java.util.Set;
@UtilityClass
public class StorageFactory {
- private static final Set TYPES = ImmutableSet.of("flatfile", "mongodb", "mysql", "sqlite", "h2");
+ private static final Set TYPES = ImmutableSet.of("json", "yaml", "flatfile", "mongodb", "mysql", "sqlite", "h2");
@SuppressWarnings("unchecked")
public static Datastore getDatastore(LuckPermsPlugin plugin, String defaultMethod) {
@@ -93,8 +93,10 @@ public class StorageFactory {
return new H2Datastore(plugin, new File(plugin.getDataFolder(), "luckperms.db"));
case "mongodb":
return new MongoDBDatastore(plugin, plugin.getConfiguration().getDatabaseValues());
+ case "yaml":
+ return new YAMLDatastore(plugin, plugin.getDataFolder());
default:
- return new FlatfileDatastore(plugin, plugin.getDataFolder());
+ return new JSONDatastore(plugin, plugin.getDataFolder());
}
}
}
diff --git a/common/src/main/java/me/lucko/luckperms/storage/methods/FlatfileDatastore.java b/common/src/main/java/me/lucko/luckperms/storage/methods/FlatfileDatastore.java
index e267a1fa..4bb62d09 100644
--- a/common/src/main/java/me/lucko/luckperms/storage/methods/FlatfileDatastore.java
+++ b/common/src/main/java/me/lucko/luckperms/storage/methods/FlatfileDatastore.java
@@ -22,75 +22,37 @@
package me.lucko.luckperms.storage.methods;
-import com.google.gson.stream.JsonReader;
-import com.google.gson.stream.JsonWriter;
import lombok.Cleanup;
import me.lucko.luckperms.LuckPermsPlugin;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.constants.Constants;
import me.lucko.luckperms.data.Log;
-import me.lucko.luckperms.groups.Group;
import me.lucko.luckperms.storage.Datastore;
-import me.lucko.luckperms.tracks.Track;
-import me.lucko.luckperms.users.User;
-import me.lucko.luckperms.utils.Node;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.*;
import java.util.logging.Formatter;
-import java.util.stream.Collectors;
-import static me.lucko.luckperms.core.PermissionHolder.exportToLegacy;
-
-@SuppressWarnings({"ResultOfMethodCallIgnored", "UnnecessaryLocalVariable"})
-public class FlatfileDatastore extends Datastore {
+abstract class FlatfileDatastore extends Datastore {
private static final String LOG_FORMAT = "%s(%s): [%s] %s(%s) --> %s";
private final Logger actionLogger = Logger.getLogger("lp_actions");
private Map uuidCache = new ConcurrentHashMap<>();
- private final File pluginDir;
- private File usersDir;
- private File groupsDir;
- private File tracksDir;
- private File uuidData;
- private File actionLog;
+ final File pluginDir;
+ File usersDir;
+ File groupsDir;
+ File tracksDir;
+ File uuidData;
+ File actionLog;
- public FlatfileDatastore(LuckPermsPlugin plugin, File pluginDir) {
- super(plugin, "Flatfile - JSON");
+ FlatfileDatastore(LuckPermsPlugin plugin, String name, File pluginDir) {
+ super(plugin, name);
this.pluginDir = pluginDir;
}
- private boolean doWrite(File file, WriteOperation writeOperation) {
- boolean success = false;
- try {
- @Cleanup FileWriter fileWriter = new FileWriter(file);
- @Cleanup BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
- @Cleanup JsonWriter jsonWriter = new JsonWriter(bufferedWriter);
- jsonWriter.setIndent(" ");
- success = writeOperation.onRun(jsonWriter);
- jsonWriter.flush();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return success;
- }
-
- private boolean doRead(File file, ReadOperation readOperation) {
- boolean success = false;
- try {
- @Cleanup FileReader fileReader = new FileReader(file);
- @Cleanup BufferedReader bufferedReader = new BufferedReader(fileReader);
- @Cleanup JsonReader jsonReader = new JsonReader(bufferedReader);
- success = readOperation.onRun(jsonReader);
- } catch (IOException e) {
- e.printStackTrace();
- }
- return success;
- }
-
@Override
public void init() {
try {
@@ -120,9 +82,17 @@ public class FlatfileDatastore extends Datastore {
cleanupUsers();
+ try {
+ childInit();
+ } catch (Exception e) {
+ return;
+ }
+
setAcceptingLogins(true);
}
+ protected abstract void childInit() throws Exception;
+
private void makeFiles() throws IOException {
File data = new File(pluginDir, "data");
data.mkdirs();
@@ -167,411 +137,6 @@ public class FlatfileDatastore extends Datastore {
return Log.builder().build();
}
- @Override
- public boolean loadUser(UUID uuid, String username) {
- User user = plugin.getUserManager().make(uuid, username);
- boolean success = false;
-
- File userFile = new File(usersDir, uuid.toString() + ".json");
- if (userFile.exists()) {
- final String[] name = new String[1];
- success = doRead(userFile, reader -> {
- reader.beginObject();
- reader.nextName(); // uuid record
- reader.nextString(); // uuid
- reader.nextName(); // name record
- name[0] = reader.nextString(); // name
- reader.nextName(); // primaryGroup record
- user.setPrimaryGroup(reader.nextString()); // primaryGroup
- reader.nextName(); //perms
- reader.beginObject();
- while (reader.hasNext()) {
- String node = reader.nextName();
- boolean b = reader.nextBoolean();
- user.getNodes().add(Node.fromSerialisedNode(node, b));
- }
-
- reader.endObject();
- reader.endObject();
- return true;
- });
-
- if (user.getName().equalsIgnoreCase("null")) {
- user.setName(name[0]);
- } else {
- if (!name[0].equals(user.getName())) {
- doWrite(userFile, writer -> {
- writer.beginObject();
- writer.name("uuid").value(user.getUuid().toString());
- writer.name("name").value(user.getName());
- writer.name("primaryGroup").value(user.getPrimaryGroup());
- writer.name("perms");
- writer.beginObject();
- for (Map.Entry e : exportToLegacy(user.getNodes()).entrySet()) {
- writer.name(e.getKey()).value(e.getValue().booleanValue());
- }
- writer.endObject();
- writer.endObject();
- return true;
- });
- }
- }
-
- } else {
- success = true;
- }
-
- if (success) plugin.getUserManager().updateOrSet(user);
- return success;
- }
-
- @Override
- public boolean saveUser(User user) {
- File userFile = new File(usersDir, user.getUuid().toString() + ".json");
- if (!plugin.getUserManager().shouldSave(user)) {
- if (userFile.exists()) {
- userFile.delete();
- }
- return true;
- }
-
- if (!userFile.exists()) {
- try {
- userFile.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- return false;
- }
- }
-
- boolean success = doWrite(userFile, writer -> {
- writer.beginObject();
- writer.name("uuid").value(user.getUuid().toString());
- writer.name("name").value(user.getName());
- writer.name("primaryGroup").value(user.getPrimaryGroup());
- writer.name("perms");
- writer.beginObject();
- for (Map.Entry e : exportToLegacy(user.getNodes()).entrySet()) {
- writer.name(e.getKey()).value(e.getValue().booleanValue());
- }
- writer.endObject();
- writer.endObject();
- return true;
- });
- return success;
- }
-
- @Override
- public boolean cleanupUsers() {
- File[] files = usersDir.listFiles((dir, name) -> name.endsWith(".json"));
- if (files == null) return false;
-
- for (File file : files) {
- Map nodes = new HashMap<>();
- doRead(file, reader -> {
- reader.beginObject();
- reader.nextName(); // uuid record
- reader.nextString(); // uuid
- reader.nextName(); // name record
- reader.nextString(); // name
- reader.nextName(); // primaryGroup record
- reader.nextString(); // primaryGroup
- reader.nextName(); //perms
- reader.beginObject();
- while (reader.hasNext()) {
- String node = reader.nextName();
- boolean b = reader.nextBoolean();
- nodes.put(node, b);
- }
-
- reader.endObject();
- reader.endObject();
- return true;
- });
-
- boolean shouldDelete = false;
- if (nodes.size() == 1) {
- for (Map.Entry e : nodes.entrySet()) {
- // There's only one
- shouldDelete = e.getKey().equalsIgnoreCase("group.default") && e.getValue();
- }
- }
-
- if (shouldDelete) {
- file.delete();
- }
- }
- return true;
- }
-
- @Override
- public Set getUniqueUsers() {
- String[] fileNames = usersDir.list((dir, name) -> name.endsWith(".json"));
- if (fileNames == null) return null;
- return Arrays.stream(fileNames)
- .map(s -> s.substring(0, s.length() - 5))
- .map(UUID::fromString)
- .collect(Collectors.toSet());
- }
-
- @Override
- public boolean createAndLoadGroup(String name) {
- Group group = plugin.getGroupManager().make(name);
-
- File groupFile = new File(groupsDir, name + ".json");
- if (!groupFile.exists()) {
- try {
- groupFile.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- return false;
- }
-
- boolean success = doWrite(groupFile, writer -> {
- writer.beginObject();
- writer.name("name").value(group.getName());
- writer.name("perms");
- writer.beginObject();
- for (Map.Entry e : exportToLegacy(group.getNodes()).entrySet()) {
- writer.name(e.getKey()).value(e.getValue().booleanValue());
- }
- writer.endObject();
- writer.endObject();
- return true;
- });
-
- if (!success) return false;
- }
-
- boolean success = doRead(groupFile, reader -> {
- reader.beginObject();
- reader.nextName(); // name record
- reader.nextString(); // name
- reader.nextName(); //perms
- reader.beginObject();
- while (reader.hasNext()) {
- String node = reader.nextName();
- boolean b = reader.nextBoolean();
- group.getNodes().add(Node.fromSerialisedNode(node, b));
- }
-
- reader.endObject();
- reader.endObject();
- return true;
- });
-
- if (success) plugin.getGroupManager().updateOrSet(group);
- return success;
- }
-
- @Override
- public boolean loadGroup(String name) {
- Group group = plugin.getGroupManager().make(name);
-
- File groupFile = new File(groupsDir, name + ".json");
- if (!groupFile.exists()) {
- return false;
- }
-
- boolean success = doRead(groupFile, reader -> {
- reader.beginObject();
- reader.nextName(); // name record
- reader.nextString(); // name
- reader.nextName(); //perms
- reader.beginObject();
- while (reader.hasNext()) {
- String node = reader.nextName();
- boolean b = reader.nextBoolean();
- group.getNodes().add(Node.fromSerialisedNode(node, b));
- }
-
- reader.endObject();
- reader.endObject();
- return true;
- });
-
- if (success) plugin.getGroupManager().updateOrSet(group);
- return success;
- }
-
- @Override
- public boolean loadAllGroups() {
- String[] fileNames = groupsDir.list((dir, name) -> name.endsWith(".json"));
- if (fileNames == null) return false;
- List groups = Arrays.stream(fileNames)
- .map(s -> s.substring(0, s.length() - 5))
- .collect(Collectors.toList());
-
- plugin.getGroupManager().unloadAll();
- groups.forEach(this::loadGroup);
- return true;
- }
-
- @Override
- public boolean saveGroup(Group group) {
- File groupFile = new File(groupsDir, group.getName() + ".json");
- if (!groupFile.exists()) {
- try {
- groupFile.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- return false;
- }
- }
-
- boolean success = doWrite(groupFile, writer -> {
- writer.beginObject();
- writer.name("name").value(group.getName());
- writer.name("perms");
- writer.beginObject();
- for (Map.Entry e : exportToLegacy(group.getNodes()).entrySet()) {
- writer.name(e.getKey()).value(e.getValue().booleanValue());
- }
- writer.endObject();
- writer.endObject();
- return true;
- });
-
- return success;
- }
-
- @Override
- public boolean deleteGroup(Group group) {
- File groupFile = new File(groupsDir, group.getName() + ".json");
- if (groupFile.exists()) {
- groupFile.delete();
- }
- return true;
- }
-
- @Override
- public boolean createAndLoadTrack(String name) {
- Track track = plugin.getTrackManager().make(name);
- List groups = new ArrayList<>();
-
- File trackFile = new File(tracksDir, name + ".json");
- if (!trackFile.exists()) {
- try {
- trackFile.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- return false;
- }
-
- boolean success = doWrite(trackFile, writer -> {
- writer.beginObject();
- writer.name("name").value(track.getName());
- writer.name("groups");
- writer.beginArray();
- for (String s : track.getGroups()) {
- writer.value(s);
- }
- writer.endArray();
- writer.endObject();
- return true;
- });
-
- if (!success) return false;
- }
-
- boolean success = doRead(trackFile, reader -> {
- reader.beginObject();
- reader.nextName(); // name record
- reader.nextString(); // name
- reader.nextName(); // groups record
- reader.beginArray();
- while (reader.hasNext()) {
- groups.add(reader.nextString());
- }
- reader.endArray();
- reader.endObject();
- return true;
- });
-
- track.setGroups(groups);
- if (success) plugin.getTrackManager().updateOrSet(track);
- return success;
- }
-
- @Override
- public boolean loadTrack(String name) {
- Track track = plugin.getTrackManager().make(name);
- List groups = new ArrayList<>();
-
- File trackFile = new File(tracksDir, name + ".json");
- if (!trackFile.exists()) {
- return false;
- }
-
- boolean success = doRead(trackFile, reader -> {
- reader.beginObject();
- reader.nextName(); // name record
- reader.nextString(); // name
- reader.nextName(); // groups record
- reader.beginArray();
- while (reader.hasNext()) {
- groups.add(reader.nextString());
- }
- reader.endArray();
- reader.endObject();
- return true;
- });
-
- track.setGroups(groups);
- if (success) plugin.getTrackManager().updateOrSet(track);
- return success;
- }
-
- @Override
- public boolean loadAllTracks() {
- String[] fileNames = tracksDir.list((dir, name) -> name.endsWith(".json"));
- if (fileNames == null) return false;
- List tracks = Arrays.stream(fileNames)
- .map(s -> s.substring(0, s.length() - 5))
- .collect(Collectors.toList());
-
- plugin.getTrackManager().unloadAll();
- tracks.forEach(this::loadTrack);
- return true;
- }
-
- @Override
- public boolean saveTrack(Track track) {
- File trackFile = new File(tracksDir, track.getName() + ".json");
- if (!trackFile.exists()) {
- try {
- trackFile.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- return false;
- }
- }
-
- boolean success = doWrite(trackFile, writer -> {
- writer.beginObject();
- writer.name("name").value(track.getName());
- writer.name("groups");
- writer.beginArray();
- for (String s : track.getGroups()) {
- writer.value(s);
- }
- writer.endArray();
- writer.endObject();
- return true;
- });
-
- return success;
- }
-
- @Override
- public boolean deleteTrack(Track track) {
- File trackFile = new File(tracksDir, track.getName() + ".json");
- if (trackFile.exists()) {
- trackFile.delete();
- }
- return true;
- }
-
private Map getUUIDCache() {
Map cache = new HashMap<>();
@@ -628,12 +193,4 @@ public class FlatfileDatastore extends Datastore {
}
return null;
}
-
- interface WriteOperation {
- boolean onRun(JsonWriter writer) throws IOException;
- }
-
- interface ReadOperation {
- boolean onRun(JsonReader reader) throws IOException;
- }
}
diff --git a/common/src/main/java/me/lucko/luckperms/storage/methods/JSONDatastore.java b/common/src/main/java/me/lucko/luckperms/storage/methods/JSONDatastore.java
new file mode 100644
index 00000000..2c00477a
--- /dev/null
+++ b/common/src/main/java/me/lucko/luckperms/storage/methods/JSONDatastore.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (c) 2016 Lucko (Luck)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.storage.methods;
+
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import lombok.Cleanup;
+import me.lucko.luckperms.LuckPermsPlugin;
+import me.lucko.luckperms.api.LogEntry;
+import me.lucko.luckperms.constants.Constants;
+import me.lucko.luckperms.data.Log;
+import me.lucko.luckperms.groups.Group;
+import me.lucko.luckperms.storage.Datastore;
+import me.lucko.luckperms.tracks.Track;
+import me.lucko.luckperms.users.User;
+import me.lucko.luckperms.utils.Node;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.*;
+import java.util.logging.Formatter;
+import java.util.stream.Collectors;
+
+import static me.lucko.luckperms.core.PermissionHolder.exportToLegacy;
+
+@SuppressWarnings({"ResultOfMethodCallIgnored", "UnnecessaryLocalVariable"})
+public class JSONDatastore extends FlatfileDatastore {
+ public JSONDatastore(LuckPermsPlugin plugin, File pluginDir) {
+ super(plugin, "Flatfile - JSON", pluginDir);
+ }
+
+ private boolean doWrite(File file, WriteOperation writeOperation) {
+ boolean success = false;
+ try {
+ @Cleanup FileWriter fileWriter = new FileWriter(file);
+ @Cleanup BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
+ @Cleanup JsonWriter jsonWriter = new JsonWriter(bufferedWriter);
+ jsonWriter.setIndent(" ");
+ success = writeOperation.onRun(jsonWriter);
+ jsonWriter.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return success;
+ }
+
+ private boolean doRead(File file, ReadOperation readOperation) {
+ boolean success = false;
+ try {
+ @Cleanup FileReader fileReader = new FileReader(file);
+ @Cleanup BufferedReader bufferedReader = new BufferedReader(fileReader);
+ @Cleanup JsonReader jsonReader = new JsonReader(bufferedReader);
+ success = readOperation.onRun(jsonReader);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return success;
+ }
+
+ @Override
+ protected void childInit() throws Exception {
+
+ }
+
+ @Override
+ public boolean loadUser(UUID uuid, String username) {
+ User user = plugin.getUserManager().make(uuid, username);
+ boolean success = false;
+
+ File userFile = new File(usersDir, uuid.toString() + ".json");
+ if (userFile.exists()) {
+ final String[] name = new String[1];
+ success = doRead(userFile, reader -> {
+ reader.beginObject();
+ reader.nextName(); // uuid record
+ reader.nextString(); // uuid
+ reader.nextName(); // name record
+ name[0] = reader.nextString(); // name
+ reader.nextName(); // primaryGroup record
+ user.setPrimaryGroup(reader.nextString()); // primaryGroup
+ reader.nextName(); //perms
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String node = reader.nextName();
+ boolean b = reader.nextBoolean();
+ user.getNodes().add(Node.fromSerialisedNode(node, b));
+ }
+
+ reader.endObject();
+ reader.endObject();
+ return true;
+ });
+
+ if (user.getName().equalsIgnoreCase("null")) {
+ user.setName(name[0]);
+ } else {
+ if (!name[0].equals(user.getName())) {
+ doWrite(userFile, writer -> {
+ writer.beginObject();
+ writer.name("uuid").value(user.getUuid().toString());
+ writer.name("name").value(user.getName());
+ writer.name("primaryGroup").value(user.getPrimaryGroup());
+ writer.name("perms");
+ writer.beginObject();
+ for (Map.Entry e : exportToLegacy(user.getNodes()).entrySet()) {
+ writer.name(e.getKey()).value(e.getValue().booleanValue());
+ }
+ writer.endObject();
+ writer.endObject();
+ return true;
+ });
+ }
+ }
+
+ } else {
+ success = true;
+ }
+
+ if (success) plugin.getUserManager().updateOrSet(user);
+ return success;
+ }
+
+ @Override
+ public boolean saveUser(User user) {
+ File userFile = new File(usersDir, user.getUuid().toString() + ".json");
+ if (!plugin.getUserManager().shouldSave(user)) {
+ if (userFile.exists()) {
+ userFile.delete();
+ }
+ return true;
+ }
+
+ if (!userFile.exists()) {
+ try {
+ userFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ boolean success = doWrite(userFile, writer -> {
+ writer.beginObject();
+ writer.name("uuid").value(user.getUuid().toString());
+ writer.name("name").value(user.getName());
+ writer.name("primaryGroup").value(user.getPrimaryGroup());
+ writer.name("perms");
+ writer.beginObject();
+ for (Map.Entry e : exportToLegacy(user.getNodes()).entrySet()) {
+ writer.name(e.getKey()).value(e.getValue().booleanValue());
+ }
+ writer.endObject();
+ writer.endObject();
+ return true;
+ });
+ return success;
+ }
+
+ @Override
+ public boolean cleanupUsers() {
+ File[] files = usersDir.listFiles((dir, name) -> name.endsWith(".json"));
+ if (files == null) return false;
+
+ for (File file : files) {
+ Map nodes = new HashMap<>();
+ doRead(file, reader -> {
+ reader.beginObject();
+ reader.nextName(); // uuid record
+ reader.nextString(); // uuid
+ reader.nextName(); // name record
+ reader.nextString(); // name
+ reader.nextName(); // primaryGroup record
+ reader.nextString(); // primaryGroup
+ reader.nextName(); //perms
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String node = reader.nextName();
+ boolean b = reader.nextBoolean();
+ nodes.put(node, b);
+ }
+
+ reader.endObject();
+ reader.endObject();
+ return true;
+ });
+
+ boolean shouldDelete = false;
+ if (nodes.size() == 1) {
+ for (Map.Entry e : nodes.entrySet()) {
+ // There's only one
+ shouldDelete = e.getKey().equalsIgnoreCase("group.default") && e.getValue();
+ }
+ }
+
+ if (shouldDelete) {
+ file.delete();
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Set getUniqueUsers() {
+ String[] fileNames = usersDir.list((dir, name) -> name.endsWith(".json"));
+ if (fileNames == null) return null;
+ return Arrays.stream(fileNames)
+ .map(s -> s.substring(0, s.length() - 5))
+ .map(UUID::fromString)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public boolean createAndLoadGroup(String name) {
+ Group group = plugin.getGroupManager().make(name);
+
+ File groupFile = new File(groupsDir, name + ".json");
+ if (!groupFile.exists()) {
+ try {
+ groupFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ boolean success = doWrite(groupFile, writer -> {
+ writer.beginObject();
+ writer.name("name").value(group.getName());
+ writer.name("perms");
+ writer.beginObject();
+ for (Map.Entry e : exportToLegacy(group.getNodes()).entrySet()) {
+ writer.name(e.getKey()).value(e.getValue().booleanValue());
+ }
+ writer.endObject();
+ writer.endObject();
+ return true;
+ });
+
+ if (!success) return false;
+ }
+
+ boolean success = doRead(groupFile, reader -> {
+ reader.beginObject();
+ reader.nextName(); // name record
+ reader.nextString(); // name
+ reader.nextName(); //perms
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String node = reader.nextName();
+ boolean b = reader.nextBoolean();
+ group.getNodes().add(Node.fromSerialisedNode(node, b));
+ }
+
+ reader.endObject();
+ reader.endObject();
+ return true;
+ });
+
+ if (success) plugin.getGroupManager().updateOrSet(group);
+ return success;
+ }
+
+ @Override
+ public boolean loadGroup(String name) {
+ Group group = plugin.getGroupManager().make(name);
+
+ File groupFile = new File(groupsDir, name + ".json");
+ if (!groupFile.exists()) {
+ return false;
+ }
+
+ boolean success = doRead(groupFile, reader -> {
+ reader.beginObject();
+ reader.nextName(); // name record
+ reader.nextString(); // name
+ reader.nextName(); //perms
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String node = reader.nextName();
+ boolean b = reader.nextBoolean();
+ group.getNodes().add(Node.fromSerialisedNode(node, b));
+ }
+
+ reader.endObject();
+ reader.endObject();
+ return true;
+ });
+
+ if (success) plugin.getGroupManager().updateOrSet(group);
+ return success;
+ }
+
+ @Override
+ public boolean loadAllGroups() {
+ String[] fileNames = groupsDir.list((dir, name) -> name.endsWith(".json"));
+ if (fileNames == null) return false;
+ List groups = Arrays.stream(fileNames)
+ .map(s -> s.substring(0, s.length() - 5))
+ .collect(Collectors.toList());
+
+ plugin.getGroupManager().unloadAll();
+ groups.forEach(this::loadGroup);
+ return true;
+ }
+
+ @Override
+ public boolean saveGroup(Group group) {
+ File groupFile = new File(groupsDir, group.getName() + ".json");
+ if (!groupFile.exists()) {
+ try {
+ groupFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ boolean success = doWrite(groupFile, writer -> {
+ writer.beginObject();
+ writer.name("name").value(group.getName());
+ writer.name("perms");
+ writer.beginObject();
+ for (Map.Entry e : exportToLegacy(group.getNodes()).entrySet()) {
+ writer.name(e.getKey()).value(e.getValue().booleanValue());
+ }
+ writer.endObject();
+ writer.endObject();
+ return true;
+ });
+
+ return success;
+ }
+
+ @Override
+ public boolean deleteGroup(Group group) {
+ File groupFile = new File(groupsDir, group.getName() + ".json");
+ if (groupFile.exists()) {
+ groupFile.delete();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean createAndLoadTrack(String name) {
+ Track track = plugin.getTrackManager().make(name);
+ List groups = new ArrayList<>();
+
+ File trackFile = new File(tracksDir, name + ".json");
+ if (!trackFile.exists()) {
+ try {
+ trackFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ boolean success = doWrite(trackFile, writer -> {
+ writer.beginObject();
+ writer.name("name").value(track.getName());
+ writer.name("groups");
+ writer.beginArray();
+ for (String s : track.getGroups()) {
+ writer.value(s);
+ }
+ writer.endArray();
+ writer.endObject();
+ return true;
+ });
+
+ if (!success) return false;
+ }
+
+ boolean success = doRead(trackFile, reader -> {
+ reader.beginObject();
+ reader.nextName(); // name record
+ reader.nextString(); // name
+ reader.nextName(); // groups record
+ reader.beginArray();
+ while (reader.hasNext()) {
+ groups.add(reader.nextString());
+ }
+ reader.endArray();
+ reader.endObject();
+ return true;
+ });
+
+ track.setGroups(groups);
+ if (success) plugin.getTrackManager().updateOrSet(track);
+ return success;
+ }
+
+ @Override
+ public boolean loadTrack(String name) {
+ Track track = plugin.getTrackManager().make(name);
+ List groups = new ArrayList<>();
+
+ File trackFile = new File(tracksDir, name + ".json");
+ if (!trackFile.exists()) {
+ return false;
+ }
+
+ boolean success = doRead(trackFile, reader -> {
+ reader.beginObject();
+ reader.nextName(); // name record
+ reader.nextString(); // name
+ reader.nextName(); // groups record
+ reader.beginArray();
+ while (reader.hasNext()) {
+ groups.add(reader.nextString());
+ }
+ reader.endArray();
+ reader.endObject();
+ return true;
+ });
+
+ track.setGroups(groups);
+ if (success) plugin.getTrackManager().updateOrSet(track);
+ return success;
+ }
+
+ @Override
+ public boolean loadAllTracks() {
+ String[] fileNames = tracksDir.list((dir, name) -> name.endsWith(".json"));
+ if (fileNames == null) return false;
+ List tracks = Arrays.stream(fileNames)
+ .map(s -> s.substring(0, s.length() - 5))
+ .collect(Collectors.toList());
+
+ plugin.getTrackManager().unloadAll();
+ tracks.forEach(this::loadTrack);
+ return true;
+ }
+
+ @Override
+ public boolean saveTrack(Track track) {
+ File trackFile = new File(tracksDir, track.getName() + ".json");
+ if (!trackFile.exists()) {
+ try {
+ trackFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ boolean success = doWrite(trackFile, writer -> {
+ writer.beginObject();
+ writer.name("name").value(track.getName());
+ writer.name("groups");
+ writer.beginArray();
+ for (String s : track.getGroups()) {
+ writer.value(s);
+ }
+ writer.endArray();
+ writer.endObject();
+ return true;
+ });
+
+ return success;
+ }
+
+ @Override
+ public boolean deleteTrack(Track track) {
+ File trackFile = new File(tracksDir, track.getName() + ".json");
+ if (trackFile.exists()) {
+ trackFile.delete();
+ }
+ return true;
+ }
+
+ interface WriteOperation {
+ boolean onRun(JsonWriter writer) throws IOException;
+ }
+
+ interface ReadOperation {
+ boolean onRun(JsonReader reader) throws IOException;
+ }
+}
diff --git a/common/src/main/java/me/lucko/luckperms/storage/methods/YAMLDatastore.java b/common/src/main/java/me/lucko/luckperms/storage/methods/YAMLDatastore.java
new file mode 100644
index 00000000..305b6545
--- /dev/null
+++ b/common/src/main/java/me/lucko/luckperms/storage/methods/YAMLDatastore.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2016 Lucko (Luck)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.lucko.luckperms.storage.methods;
+
+import lombok.Cleanup;
+import me.lucko.luckperms.LuckPermsPlugin;
+import me.lucko.luckperms.groups.Group;
+import me.lucko.luckperms.tracks.Track;
+import me.lucko.luckperms.users.User;
+import me.lucko.luckperms.utils.Node;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.BaseConstructor;
+
+import java.io.*;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static me.lucko.luckperms.core.PermissionHolder.exportToLegacy;
+
+@SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"})
+public class YAMLDatastore extends FlatfileDatastore {
+ public YAMLDatastore(LuckPermsPlugin plugin, File pluginDir) {
+ super(plugin, "Flatfile - YAML", pluginDir);
+ }
+
+ private Yaml getYaml() {
+ DumperOptions options = new DumperOptions();
+ options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
+ return new Yaml(options);
+ }
+
+ private boolean doRead(File file, ReadOperation readOperation) {
+ boolean success = false;
+ try {
+ @Cleanup FileReader fileReader = new FileReader(file);
+ @Cleanup BufferedReader bufferedReader = new BufferedReader(fileReader);
+ success = readOperation.onRun((Map) getYaml().load(bufferedReader));
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ return success;
+ }
+
+ private boolean doWrite(File file, Map values) {
+ try {
+ @Cleanup FileWriter fileWriter = new FileWriter(file);
+ @Cleanup BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
+ getYaml().dump(values, bufferedWriter);
+ bufferedWriter.flush();
+ return true;
+ } catch (Throwable t) {
+ t.printStackTrace();
+ return false;
+ }
+ }
+
+ @Override
+ protected void childInit() throws Exception {
+
+ }
+
+ @Override
+ public boolean loadUser(UUID uuid, String username) {
+ User user = plugin.getUserManager().make(uuid, username);
+ boolean success = false;
+
+ File userFile = new File(usersDir, uuid.toString() + ".yml");
+ if (userFile.exists()) {
+ final String[] name = {null};
+ success = doRead(userFile, values -> {
+ name[0] = (String) values.get("name");
+ user.setPrimaryGroup((String) values.get("primary-group"));
+ Map perms = (Map) values.get("perms");
+ for (Map.Entry e : perms.entrySet()) {
+ user.getNodes().add(Node.fromSerialisedNode(e.getKey(), e.getValue()));
+ }
+ return true;
+ });
+
+ if (user.getName().equalsIgnoreCase("null")) {
+ user.setName(name[0]);
+ } else {
+ if (!name[0].equals(user.getName())) {
+ Map values = new HashMap<>();
+ values.put("uuid", user.getUuid().toString());
+ values.put("name", user.getName());
+ values.put("primary-group", user.getPrimaryGroup());
+ values.put("perms", exportToLegacy(user.getNodes()));
+ doWrite(userFile, values);
+ }
+ }
+
+ } else {
+ success = true;
+ }
+
+ if (success) plugin.getUserManager().updateOrSet(user);
+ return success;
+ }
+
+ @Override
+ public boolean saveUser(User user) {
+ File userFile = new File(usersDir, user.getUuid().toString() + ".yml");
+ if (!plugin.getUserManager().shouldSave(user)) {
+ if (userFile.exists()) {
+ userFile.delete();
+ }
+ return true;
+ }
+
+ if (!userFile.exists()) {
+ try {
+ userFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ Map values = new HashMap<>();
+ values.put("uuid", user.getUuid().toString());
+ values.put("name", user.getName());
+ values.put("primary-group", user.getPrimaryGroup());
+ values.put("perms", exportToLegacy(user.getNodes()));
+ return doWrite(userFile, values);
+ }
+
+ @Override
+ public boolean cleanupUsers() {
+ File[] files = usersDir.listFiles((dir, name) -> name.endsWith(".yml"));
+ if (files == null) return false;
+
+ for (File file : files) {
+ Map nodes = new HashMap<>();
+ doRead(file, values -> {
+ Map perms = (Map) values.get("perms");
+ nodes.putAll(perms);
+ return true;
+ });
+
+ boolean shouldDelete = false;
+ if (nodes.size() == 1) {
+ for (Map.Entry e : nodes.entrySet()) {
+ // There's only one
+ shouldDelete = e.getKey().equalsIgnoreCase("group.default") && e.getValue();
+ }
+ }
+
+ if (shouldDelete) {
+ file.delete();
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Set getUniqueUsers() {
+ String[] fileNames = usersDir.list((dir, name) -> name.endsWith(".yml"));
+ if (fileNames == null) return null;
+ return Arrays.stream(fileNames)
+ .map(s -> s.substring(0, s.length() - 4))
+ .map(UUID::fromString)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public boolean createAndLoadGroup(String name) {
+ Group group = plugin.getGroupManager().make(name);
+
+ File groupFile = new File(groupsDir, name + ".yml");
+ if (!groupFile.exists()) {
+ try {
+ groupFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ Map values = new HashMap<>();
+ values.put("name", group.getName());
+ values.put("perms", exportToLegacy(group.getNodes()));
+
+ if (!doWrite(groupFile, values)) {
+ return false;
+ }
+ }
+
+ boolean success = doRead(groupFile, values -> {
+ Map perms = (Map) values.get("perms");
+ for (Map.Entry e : perms.entrySet()) {
+ group.getNodes().add(Node.fromSerialisedNode(e.getKey(), e.getValue()));
+ }
+ return true;
+ });
+
+ if (success) plugin.getGroupManager().updateOrSet(group);
+ return success;
+ }
+
+ @Override
+ public boolean loadGroup(String name) {
+ Group group = plugin.getGroupManager().make(name);
+
+ File groupFile = new File(groupsDir, name + ".yml");
+ if (!groupFile.exists()) {
+ return false;
+ }
+
+ boolean success = doRead(groupFile, values -> {
+ Map perms = (Map) values.get("perms");
+ for (Map.Entry e : perms.entrySet()) {
+ group.getNodes().add(Node.fromSerialisedNode(e.getKey(), e.getValue()));
+ }
+ return true;
+ });
+
+ if (success) plugin.getGroupManager().updateOrSet(group);
+ return success;
+ }
+
+ @Override
+ public boolean loadAllGroups() {
+ String[] fileNames = groupsDir.list((dir, name) -> name.endsWith(".yml"));
+ if (fileNames == null) return false;
+ List groups = Arrays.stream(fileNames)
+ .map(s -> s.substring(0, s.length() - 4))
+ .collect(Collectors.toList());
+
+ plugin.getGroupManager().unloadAll();
+ groups.forEach(this::loadGroup);
+ return true;
+ }
+
+ @Override
+ public boolean saveGroup(Group group) {
+ File groupFile = new File(groupsDir, group.getName() + ".yml");
+ if (!groupFile.exists()) {
+ try {
+ groupFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ Map values = new HashMap<>();
+ values.put("name", group.getName());
+ values.put("perms", exportToLegacy(group.getNodes()));
+ return doWrite(groupFile, values);
+ }
+
+ @Override
+ public boolean deleteGroup(Group group) {
+ File groupFile = new File(groupsDir, group.getName() + ".yml");
+ if (groupFile.exists()) {
+ groupFile.delete();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean createAndLoadTrack(String name) {
+ Track track = plugin.getTrackManager().make(name);
+ List groups = new ArrayList<>();
+
+ File trackFile = new File(tracksDir, name + ".yml");
+ if (!trackFile.exists()) {
+ try {
+ trackFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ Map values = new HashMap<>();
+ values.put("name", track.getName());
+ values.put("groups", track.getGroups());
+
+ if (!doWrite(trackFile, values)) {
+ return false;
+ }
+ }
+
+ boolean success = doRead(trackFile, values -> {
+ groups.addAll((List) values.get("groups"));
+ return true;
+ });
+
+ track.setGroups(groups);
+ if (success) plugin.getTrackManager().updateOrSet(track);
+ return success;
+ }
+
+ @Override
+ public boolean loadTrack(String name) {
+ Track track = plugin.getTrackManager().make(name);
+ List groups = new ArrayList<>();
+
+ File trackFile = new File(tracksDir, name + ".yml");
+ if (!trackFile.exists()) {
+ return false;
+ }
+
+ boolean success = doRead(trackFile, values -> {
+ groups.addAll((List) values.get("groups"));
+ return true;
+ });
+
+ track.setGroups(groups);
+ if (success) plugin.getTrackManager().updateOrSet(track);
+ return success;
+ }
+
+ @Override
+ public boolean loadAllTracks() {
+ String[] fileNames = tracksDir.list((dir, name) -> name.endsWith(".yml"));
+ if (fileNames == null) return false;
+ List tracks = Arrays.stream(fileNames)
+ .map(s -> s.substring(0, s.length() - 4))
+ .collect(Collectors.toList());
+
+ plugin.getTrackManager().unloadAll();
+ tracks.forEach(this::loadTrack);
+ return true;
+ }
+
+ @Override
+ public boolean saveTrack(Track track) {
+ File trackFile = new File(tracksDir, track.getName() + ".yml");
+ if (!trackFile.exists()) {
+ try {
+ trackFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ Map values = new HashMap<>();
+ values.put("name", track.getName());
+ values.put("groups", track.getGroups());
+ return doWrite(trackFile, values);
+ }
+
+ @Override
+ public boolean deleteTrack(Track track) {
+ File trackFile = new File(tracksDir, track.getName() + ".yml");
+ if (trackFile.exists()) {
+ trackFile.delete();
+ }
+ return true;
+ }
+
+ interface ReadOperation {
+ boolean onRun(Map values) throws IOException;
+ }
+}
diff --git a/sponge/src/main/resources/luckperms.conf b/sponge/src/main/resources/luckperms.conf
index 6190cc9f..1c2de149 100644
--- a/sponge/src/main/resources/luckperms.conf
+++ b/sponge/src/main/resources/luckperms.conf
@@ -43,7 +43,7 @@ apply-shorthand=true
log-notify=true
# Which storage method the plugin should use.
-# Currently supported: mysql, sqlite, h2, flatfile, mongodb
+# Currently supported: mysql, sqlite, h2, json, yaml, mongodb
# Fill out connection info below if you're using MySQL or MongoDB
storage-method="h2"