Add flatfile storage support

This commit is contained in:
Luck 2016-06-23 16:37:29 +01:00
parent 85989aeb7c
commit 0781637617
6 changed files with 437 additions and 4 deletions

View File

@ -10,10 +10,10 @@ A (fairly bad) permissions implementation for Bukkit/BungeeCord.
* **Easy and simple setup and configuration using commands** - no editing yml files, yuck
* **Efficient/lightweight** - maybe? Who knows, it might be.
* **BungeeCord compatible** - permissions, users and groups are synced across Bukkit/BungeeCord instances
* **Support for MySQL and SQLite** - other storage methods coming soon (maybe)
* **Support for MySQL, SQLite & Flatfile (JSON)** - other storage methods coming soon (maybe)
===== Possible Caveats
* Currently only supports MySQL and SQLite (support for more methods might come in the future)
* Currently only supports MySQL, SQLite & Flatfile (JSON) (support for more methods might come in the future)
* Not at all tested and could produce unexpected/buggy results and errors
=== Setup

View File

@ -3,6 +3,7 @@ package me.lucko.luckperms;
import lombok.Getter;
import me.lucko.luckperms.data.Datastore;
import me.lucko.luckperms.data.MySQLConfiguration;
import me.lucko.luckperms.data.methods.FlatfileDatastore;
import me.lucko.luckperms.data.methods.MySQLDatastore;
import me.lucko.luckperms.data.methods.SQLiteDatastore;
import me.lucko.luckperms.groups.GroupManager;
@ -58,6 +59,9 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
} else if (storageMethod.equalsIgnoreCase("sqlite")) {
getLogger().info("Using SQLite as storage method.");
datastore = new SQLiteDatastore(this, new File(getDataFolder(), "luckperms.sqlite"));
} else if (storageMethod.equalsIgnoreCase("flatfile")) {
getLogger().info("Using Flatfile (JSON) as storage method.");
datastore = new FlatfileDatastore(this, getDataFolder());
} else {
getLogger().warning("Storage method '" + storageMethod + "' was not recognised. Using SQLite as fallback.");
datastore = new SQLiteDatastore(this, new File(getDataFolder(), "luckperms.sqlite"));

View File

@ -12,7 +12,7 @@ include-global: true
prefix: '&7&l[&b&lL&a&lP&7&l] &c'
# Which storage method the plugin should use.
# Currently supported: mysql, sqlite
# Currently supported: mysql, sqlite, flatfile
# Fill out connection info below if you're using MySQL
storage-method: sqlite

View File

@ -4,6 +4,7 @@ import lombok.Getter;
import me.lucko.luckperms.commands.CommandManager;
import me.lucko.luckperms.data.Datastore;
import me.lucko.luckperms.data.MySQLConfiguration;
import me.lucko.luckperms.data.methods.FlatfileDatastore;
import me.lucko.luckperms.data.methods.MySQLDatastore;
import me.lucko.luckperms.data.methods.SQLiteDatastore;
import me.lucko.luckperms.groups.GroupManager;
@ -50,6 +51,9 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
} else if (storageMethod.equalsIgnoreCase("sqlite")) {
getLogger().info("Using SQLite as storage method.");
datastore = new SQLiteDatastore(this, new File(getDataFolder(), "luckperms.sqlite"));
} else if (storageMethod.equalsIgnoreCase("flatfile")) {
getLogger().info("Using Flatfile (JSON) as storage method.");
datastore = new FlatfileDatastore(this, getDataFolder());
} else {
getLogger().warning("Storage method '" + storageMethod + "' was not recognised. Using SQLite as fallback.");
datastore = new SQLiteDatastore(this, new File(getDataFolder(), "luckperms.sqlite"));

View File

@ -12,7 +12,7 @@ include-global: false
prefix: '&7&l[&b&lL&a&lP&7&l] &c'
# Which storage method the plugin should use.
# Currently supported: mysql, sqlite
# Currently supported: mysql, sqlite, flatfile
# Fill out connection info below if you're using MySQL
storage-method: sqlite

View File

@ -0,0 +1,425 @@
package me.lucko.luckperms.data.methods;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import me.lucko.luckperms.LuckPermsPlugin;
import me.lucko.luckperms.data.Datastore;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.groups.Group;
import me.lucko.luckperms.users.User;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@SuppressWarnings({"ResultOfMethodCallIgnored", "UnnecessaryLocalVariable"})
public class FlatfileDatastore extends Datastore {
private Map<String, String> uuidCache = new ConcurrentHashMap<>();
private final File pluginDir;
private File usersDir;
private File groupsDir;
private File uuidData;
public FlatfileDatastore(LuckPermsPlugin plugin, File pluginDir) {
super(plugin, "Flatfile - JSON");
this.pluginDir = pluginDir;
}
private boolean doWrite(File file, WriteOperation writeOperation) {
boolean success = false;
FileWriter fileWriter = null;
BufferedWriter bufferedWriter = null;
JsonWriter jsonWriter = null;
try {
fileWriter = new FileWriter(file);
bufferedWriter = new BufferedWriter(fileWriter);
jsonWriter = new JsonWriter(bufferedWriter);
jsonWriter.setIndent(" ");
success = writeOperation.onRun(jsonWriter);
} catch (IOException e) {
e.printStackTrace();
} finally {
close(jsonWriter);
close(bufferedWriter);
close(fileWriter);
}
return success;
}
private boolean doRead(File file, ReadOperation readOperation) {
boolean success = false;
FileReader fileReader = null;
BufferedReader bufferedReader = null;
JsonReader jsonReader = null;
try {
fileReader = new FileReader(file);
bufferedReader = new BufferedReader(fileReader);
jsonReader = new JsonReader(bufferedReader);
success = readOperation.onRun(jsonReader);
} catch (IOException e) {
e.printStackTrace();
} finally {
close(jsonReader);
close(bufferedReader);
close(fileReader);
}
return success;
}
private void close(AutoCloseable closeable) {
if (closeable == null) return;
try {
closeable.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void init() {
try {
makeFiles();
} catch (IOException e) {
// TODO catch here or something
e.printStackTrace();
return;
}
uuidCache.putAll(getUUIDCache());
}
private void makeFiles() throws IOException {
File data = new File(pluginDir, "data");
data.mkdirs();
usersDir = new File(data, "users");
usersDir.mkdir();
groupsDir = new File(data, "groups");
groupsDir.mkdir();
uuidData = new File(data, "uuidcache.txt");
uuidData.createNewFile();
}
@Override
public void shutdown() {
saveUUIDCache(uuidCache);
}
@Override
public boolean loadOrCreateUser(UUID uuid, String username) {
User user = plugin.getUserManager().makeUser(uuid, username);
try {
user.setPermission(plugin.getConfiguration().getDefaultGroupNode(), true);
} catch (ObjectAlreadyHasException ignored) {}
File userFile = new File(usersDir, uuid.toString() + ".json");
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("perms");
writer.beginObject();
for (Map.Entry<String, Boolean> e : user.getNodes().entrySet()) {
writer.name(e.getKey()).value(e.getValue().booleanValue());
}
writer.endObject();
writer.endObject();
return true;
});
if (!success) return false;
}
boolean success = doRead(userFile, reader -> {
reader.beginObject();
reader.nextName(); // uuid record
reader.nextString(); // uuid
reader.nextName(); // name record
reader.nextString(); // name
reader.nextName(); //perms
reader.beginObject();
while (reader.hasNext()) {
String node = reader.nextName();
boolean b = reader.nextBoolean();
user.getNodes().put(node, b);
}
reader.endObject();
reader.endObject();
return true;
});
// User updating and loading should be done sync as permission attachments are updated
if (success) plugin.doSync(() -> plugin.getUserManager().updateOrSetUser(user));
return success;
}
@Override
public boolean loadUser(UUID uuid) {
User user = plugin.getUserManager().makeUser(uuid);
File userFile = new File(usersDir, uuid.toString() + ".json");
if (!userFile.exists()) {
return false;
}
boolean success = doRead(userFile, reader -> {
reader.beginObject();
reader.nextName(); // uuid record
reader.nextString(); // uuid
reader.nextName(); // name record
user.setName(reader.nextString()); // name
reader.nextName(); //perms
reader.beginObject();
while (reader.hasNext()) {
String node = reader.nextName();
boolean b = reader.nextBoolean();
user.getNodes().put(node, b);
}
reader.endObject();
reader.endObject();
return true;
});
// User updating and loading should be done sync as permission attachments are updated
if (success) plugin.doSync(() -> plugin.getUserManager().updateOrSetUser(user));
return success;
}
@Override
public boolean saveUser(User user) {
File userFile = new File(usersDir, user.getUuid().toString() + ".json");
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("perms");
writer.beginObject();
for (Map.Entry<String, Boolean> e : user.getNodes().entrySet()) {
writer.name(e.getKey()).value(e.getValue().booleanValue());
}
writer.endObject();
writer.endObject();
return true;
});
return success;
}
@Override
public boolean createAndLoadGroup(String name) {
Group group = plugin.getGroupManager().makeGroup(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<String, Boolean> e : 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().put(node, b);
}
reader.endObject();
reader.endObject();
return true;
});
if (success) plugin.getGroupManager().updateOrSetGroup(group);
return success;
}
@Override
public boolean loadGroup(String name) {
Group group = plugin.getGroupManager().makeGroup(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().put(node, b);
}
reader.endObject();
reader.endObject();
return true;
});
if (success) plugin.getGroupManager().updateOrSetGroup(group);
return success;
}
@Override
public boolean loadAllGroups() {
List<String> groups = Arrays.asList(groupsDir.list((dir, name1) -> name1.endsWith(".json")));
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<String, Boolean> e : 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;
}
private Map<String, String> getUUIDCache() {
Map<String, String> cache = new HashMap<>();
FileReader fileReader = null;
BufferedReader bufferedReader = null;
try {
fileReader = new FileReader(uuidData);
bufferedReader = new BufferedReader(fileReader);
Properties props = new Properties();
props.load(bufferedReader);
for (String key : props.stringPropertyNames()) {
cache.put(key, props.getProperty(key));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close(bufferedReader);
close(fileReader);
}
return cache;
}
private void saveUUIDCache(Map<String, String> cache) {
FileWriter fileWriter = null;
BufferedWriter bufferedWriter = null;
try {
fileWriter = new FileWriter(uuidData);
bufferedWriter = new BufferedWriter(fileWriter);
Properties properties = new Properties();
properties.putAll(cache);
properties.store(bufferedWriter, null);
} catch (IOException e) {
e.printStackTrace();
} finally {
close(bufferedWriter);
close(fileWriter);
}
}
@Override
public boolean saveUUIDData(String username, UUID uuid) {
uuidCache.put(username, uuid.toString());
return true;
}
@Override
public UUID getUUID(String username) {
if (uuidCache.get(username) == null) return null;
return UUID.fromString(uuidCache.get(username));
}
interface WriteOperation {
boolean onRun(JsonWriter writer) throws IOException;
}
interface ReadOperation {
boolean onRun(JsonReader reader) throws IOException;
}
}