Add support for MongoDB and H2 database formats

This commit is contained in:
Luck 2016-08-19 18:21:16 +01:00
parent 31cef46b56
commit a15a0752f4
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
26 changed files with 877 additions and 137 deletions

View File

@ -30,7 +30,7 @@ A permissions implementation for Bukkit/Spigot, BungeeCord and Sponge.
* **Open Sourced, Free...** - you shouldn't have to pay $10+ for a "powerful" permissions plugin.
* **BungeeCord compatible** - permissions, users and groups are synced across all LuckPerms instances
* **Sponge compatible** - permissions, users and groups are synced across all LuckPerms instances (bukkit --> sponge, for example)
* **Support for MySQL, SQLite & Flatfile (JSON)** - other storage methods coming soon (maybe)
* **Support for MySQL, MongoDB, SQLite, H2 & Flatfile (JSON)** - other storage methods coming soon (maybe)
## Setup
All configuration options are in the **config.yml/luckperms.conf** file, which is generated automagically when the plugin first starts.

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>2.3</version>
<version>2.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -22,6 +22,7 @@
package me.lucko.luckperms.api;
import me.lucko.luckperms.api.data.DatastoreConfiguration;
import me.lucko.luckperms.api.data.MySQLConfiguration;
/**
@ -76,9 +77,17 @@ public interface LPConfiguration {
/**
* @return the database values set in the configuration
* @deprecated use {@link #getDatastoreConfig()}
*/
@SuppressWarnings("deprecation")
@Deprecated
MySQLConfiguration getDatabaseValues();
/**
* @return the values set for data storage in the configuration
*/
DatastoreConfiguration getDatastoreConfig();
/**
* @return the storage method string from the configuration
*/

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.api.data;
@SuppressWarnings("deprecation")
public interface DatastoreConfiguration extends MySQLConfiguration {
String getAddress();
String getDatabase();
String getUsername();
String getPassword();
}

View File

@ -22,6 +22,10 @@
package me.lucko.luckperms.api.data;
/**
* @deprecated Use {@link DatastoreConfiguration}. This is now used by multiple datastores, not just MySQL.
*/
@Deprecated
public interface MySQLConfiguration {
String getAddress();

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>2.3</version>
<version>2.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -43,7 +43,12 @@
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<minimizeJar>false</minimizeJar>
<artifactSet>
<excludes>
<exclude>org.xerial:*</exclude>
</excludes>
</artifactSet>
<relocations>
<relocation>
<pattern>org.slf4j</pattern>
@ -53,6 +58,18 @@
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern>
</relocation>
<relocation>
<pattern>com.mongodb</pattern>
<shadedPattern>me.lucko.luckperms.lib.mongodb</shadedPattern>
</relocation>
<relocation>
<pattern>org.bson</pattern>
<shadedPattern>me.lucko.luckperms.lib.bson</shadedPattern>
</relocation>
<relocation>
<pattern>org.h2</pattern>
<shadedPattern>me.lucko.luckperms.lib.h2</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
@ -105,20 +122,6 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.4.7</version>
<scope>compile</scope>
</dependency>
<!-- slf4j library -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.9</version>
<scope>compile</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>

View File

@ -35,9 +35,7 @@ import me.lucko.luckperms.data.Importer;
import me.lucko.luckperms.groups.GroupManager;
import me.lucko.luckperms.runnables.UpdateTask;
import me.lucko.luckperms.storage.Datastore;
import me.lucko.luckperms.storage.methods.FlatfileDatastore;
import me.lucko.luckperms.storage.methods.MySQLDatastore;
import me.lucko.luckperms.storage.methods.SQLiteDatastore;
import me.lucko.luckperms.storage.StorageFactory;
import me.lucko.luckperms.tracks.TrackManager;
import me.lucko.luckperms.users.BukkitUserManager;
import me.lucko.luckperms.users.UserManager;
@ -83,24 +81,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
main.setTabCompleter(commandManager);
main.setAliases(Arrays.asList("perms", "lp", "permissions", "p", "perm"));
getLog().info("Detecting storage method...");
final String storageMethod = configuration.getStorageMethod();
if (storageMethod.equalsIgnoreCase("mysql")) {
getLog().info("Using MySQL as storage method.");
datastore = new MySQLDatastore(this, configuration.getDatabaseValues());
} else if (storageMethod.equalsIgnoreCase("sqlite")) {
getLog().info("Using SQLite as storage method.");
datastore = new SQLiteDatastore(this, new File(getDataFolder(), "luckperms.sqlite"));
} else if (storageMethod.equalsIgnoreCase("flatfile")) {
getLog().info("Using Flatfile (JSON) as storage method.");
datastore = new FlatfileDatastore(this, getDataFolder());
} else {
getLog().severe("Storage method '" + storageMethod + "' was not recognised. Using SQLite as fallback.");
datastore = new SQLiteDatastore(this, new File(getDataFolder(), "luckperms.sqlite"));
}
getLog().info("Initialising datastore...");
datastore.init();
datastore = StorageFactory.getDatastore(this, "sqlite");
getLog().info("Loading internal permission managers...");
uuidCache = new UuidCache(getConfiguration().getOnlineMode());
@ -197,6 +178,11 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
return getServer().getOnlinePlayers().stream().map(p -> BukkitSenderFactory.get().wrap(p)).collect(Collectors.toList());
}
@Override
public Sender getConsoleSender() {
return BukkitSenderFactory.get().wrap(getServer().getConsoleSender());
}
@Override
public List<String> getPossiblePermissions() {
final List<String> perms = new ArrayList<>();

View File

@ -1,4 +1,9 @@
# LuckPerms Configuration
##############################################################################
# +------------------------------------------------------------------------+ #
# | LuckPerms Configuration | #
# | https://github.com/lucko/LuckPerms | #
# +------------------------------------------------------------------------+ #
##############################################################################
# The name of the server, used for server specific permissions. Set to 'global' to disable.
server: global
@ -38,13 +43,16 @@ apply-regex: true
apply-shorthand: true
# Which storage method the plugin should use.
# Currently supported: mysql, sqlite, flatfile
# Fill out connection info below if you're using MySQL
storage-method: sqlite
# Currently supported: mysql, sqlite, h2, flatfile, mongodb
# Fill out connection info below if you're using MySQL or MongoDB
storage-method: h2
sql:
data:
address: localhost:3306
database: minecraft
username: root
password: ''
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes: 3

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>2.3</version>
<version>2.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -43,7 +43,7 @@
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<minimizeJar>false</minimizeJar>
<relocations>
<relocation>
<pattern>org.slf4j</pattern>
@ -53,6 +53,18 @@
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern>
</relocation>
<relocation>
<pattern>com.mongodb</pattern>
<shadedPattern>me.lucko.luckperms.lib.mongodb</shadedPattern>
</relocation>
<relocation>
<pattern>org.bson</pattern>
<shadedPattern>me.lucko.luckperms.lib.bson</shadedPattern>
</relocation>
<relocation>
<pattern>org.h2</pattern>
<shadedPattern>me.lucko.luckperms.lib.h2</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
@ -98,20 +110,6 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.4.7</version>
<scope>compile</scope>
</dependency>
<!-- slf4j library -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.9</version>
<scope>compile</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>

View File

@ -34,8 +34,7 @@ import me.lucko.luckperms.data.Importer;
import me.lucko.luckperms.groups.GroupManager;
import me.lucko.luckperms.runnables.UpdateTask;
import me.lucko.luckperms.storage.Datastore;
import me.lucko.luckperms.storage.methods.FlatfileDatastore;
import me.lucko.luckperms.storage.methods.MySQLDatastore;
import me.lucko.luckperms.storage.StorageFactory;
import me.lucko.luckperms.tracks.TrackManager;
import me.lucko.luckperms.users.BungeeUserManager;
import me.lucko.luckperms.users.UserManager;
@ -78,21 +77,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
// disable the default Bungee /perms command so it gets handled by the Bukkit plugin
getProxy().getDisabledCommands().add("perms");
getLog().info("Detecting storage method...");
final String storageMethod = configuration.getStorageMethod();
if (storageMethod.equalsIgnoreCase("mysql")) {
getLog().info("Using MySQL as storage method.");
datastore = new MySQLDatastore(this, configuration.getDatabaseValues());
} else if (storageMethod.equalsIgnoreCase("flatfile")) {
getLog().info("Using Flatfile (JSON) as storage method.");
datastore = new FlatfileDatastore(this, getDataFolder());
} else {
getLog().severe("Storage method '" + storageMethod + "' was not recognised. Using Flatfile as fallback.");
datastore = new FlatfileDatastore(this, getDataFolder());
}
getLog().info("Initialising datastore...");
datastore.init();
datastore = StorageFactory.getDatastore(this, "h2");
getLog().info("Loading internal permission managers...");
uuidCache = new UuidCache(getConfiguration().getOnlineMode());
@ -162,6 +147,11 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
return getProxy().getPlayers().stream().map(p -> BungeeSenderFactory.get().wrap(p)).collect(Collectors.toList());
}
@Override
public Sender getConsoleSender() {
return BungeeSenderFactory.get().wrap(getProxy().getConsole());
}
@Override
public List<String> getPossiblePermissions() {
// No such thing on Bungee. Wildcards are processed in the listener instead.

View File

@ -1,4 +1,9 @@
# LuckPerms Configuration
##############################################################################
# +------------------------------------------------------------------------+ #
# | LuckPerms Configuration | #
# | https://github.com/lucko/LuckPerms | #
# +------------------------------------------------------------------------+ #
##############################################################################
# The name of the server, used for server specific permissions. Set to 'global' to disable.
server: bungee
@ -38,13 +43,16 @@ apply-regex: true
apply-shorthand: true
# Which storage method the plugin should use.
# Currently supported: mysql & flatfile
# Fill out connection info below if you're using MySQL
storage-method: flatfile
# Currently supported: mysql, sqlite, h2, flatfile, mongodb
# Fill out connection info below if you're using MySQL or MongoDB
storage-method: h2
sql:
data:
address: localhost:3306
database: minecraft
username: root
password: ''
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes: 3

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>2.3</version>
<version>2.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -39,6 +39,27 @@
<version>2.4.7</version>
<scope>compile</scope>
</dependency>
<!-- SQLite -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.8.11.2</version>
<scope>compile</scope>
</dependency>
<!-- H2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.192</version>
<scope>compile</scope>
</dependency>
<!-- MongoDB -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.3.0</version>
<scope>compile</scope>
</dependency>
<!-- slf4j library -->
<dependency>
<groupId>org.slf4j</groupId>

View File

@ -96,6 +96,11 @@ public interface LuckPermsPlugin {
*/
File getMainDir();
/**
* @return the platforms data folder
*/
File getDataFolder();
/**
* @return the importer instance for the platform
*/
@ -126,6 +131,12 @@ public interface LuckPermsPlugin {
*/
List<Sender> getSenders();
/**
* Gets the console sender of the instance
* @return a the console sender of the instance
*/
Sender getConsoleSender();
/**
* Gets all possible permission nodes, used for resolving wildcards
* @return a {@link List} of permission nodes

View File

@ -24,6 +24,7 @@ package me.lucko.luckperms.api.implementation.internal;
import lombok.AllArgsConstructor;
import me.lucko.luckperms.api.LPConfiguration;
import me.lucko.luckperms.api.data.DatastoreConfiguration;
import me.lucko.luckperms.api.data.MySQLConfiguration;
/**
@ -78,8 +79,14 @@ public class LPConfigurationLink implements LPConfiguration {
return master.getApplyShorthand();
}
@SuppressWarnings("deprecation")
@Override
public MySQLConfiguration getDatabaseValues() {
return getDatastoreConfig();
}
@Override
public DatastoreConfiguration getDatastoreConfig() {
return master.getDatabaseValues();
}

View File

@ -26,7 +26,7 @@ import lombok.AccessLevel;
import lombok.Getter;
import me.lucko.luckperms.LuckPermsPlugin;
import me.lucko.luckperms.constants.Patterns;
import me.lucko.luckperms.storage.MySQLConfiguration;
import me.lucko.luckperms.storage.DatastoreConfiguration;
public abstract class LPConfiguration<T extends LuckPermsPlugin> {
@ -70,7 +70,7 @@ public abstract class LPConfiguration<T extends LuckPermsPlugin> {
}
public int getSyncTime() {
return getInt("sql.sync-minutes", 3);
return getInt("data.sync-minutes", 3);
}
public String getDefaultGroupNode() {
@ -101,12 +101,12 @@ public abstract class LPConfiguration<T extends LuckPermsPlugin> {
return getBoolean("apply-shorthand", true);
}
public MySQLConfiguration getDatabaseValues() {
return new MySQLConfiguration(
getString("sql.address", null),
getString("sql.database", null),
getString("sql.username", null),
getString("sql.password", null)
public DatastoreConfiguration getDatabaseValues() {
return new DatastoreConfiguration(
getString("data.address", null),
getString("data.database", null),
getString("data.username", null),
getString("data.password", null)
);
}

View File

@ -31,6 +31,9 @@ import me.lucko.luckperms.groups.Group;
import me.lucko.luckperms.tracks.Track;
import me.lucko.luckperms.users.User;
import java.util.List;
import java.util.stream.Collectors;
public class LogEntry extends me.lucko.luckperms.api.LogEntry {
public static LogEntryBuilder build() {
return new LogEntryBuilder();
@ -45,8 +48,12 @@ public class LogEntry extends me.lucko.luckperms.api.LogEntry {
final String msg = super.getFormatted();
plugin.getSenders().stream()
List<Sender> senders = plugin.getSenders().stream()
.filter(Permission.LOG_NOTIFY::isAuthorized)
.collect(Collectors.toList());
senders.add(plugin.getConsoleSender());
senders.stream()
.filter(s -> !plugin.getIgnoringLogs().contains(s.getUuid()))
.forEach(s -> Message.LOG.send(s, msg));
}

View File

@ -27,7 +27,7 @@ import lombok.Getter;
@Getter
@AllArgsConstructor
public class MySQLConfiguration implements me.lucko.luckperms.api.data.MySQLConfiguration {
public class DatastoreConfiguration implements me.lucko.luckperms.api.data.DatastoreConfiguration {
private final String address;
private final String database;

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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;
import com.google.common.collect.ImmutableSet;
import lombok.experimental.UtilityClass;
import me.lucko.luckperms.LuckPermsPlugin;
import me.lucko.luckperms.storage.methods.*;
import java.io.File;
import java.util.Set;
@UtilityClass
public class StorageFactory {
private static final Set<String> TYPES = ImmutableSet.of("flatfile", "mongodb", "mysql", "sqlite", "h2");
public static Datastore getDatastore(LuckPermsPlugin plugin, String defaultMethod) {
Datastore datastore;
plugin.getLog().info("Detecting storage method...");
String storageMethod = plugin.getConfiguration().getStorageMethod().toLowerCase();
if (!TYPES.contains(storageMethod)) {
plugin.getLog().severe("Storage method '" + storageMethod + "' not recognised. Using the default instead.");
storageMethod = defaultMethod;
}
switch (storageMethod) {
case "mysql":
plugin.getLog().info("Using MySQL as storage method.");
datastore = new MySQLDatastore(plugin, plugin.getConfiguration().getDatabaseValues());
break;
case "sqlite":
plugin.getLog().info("Using SQLite as storage method.");
datastore = new SQLiteDatastore(plugin, new File(plugin.getDataFolder(), "luckperms.sqlite"));
break;
case "h2":
plugin.getLog().info("Using H2 as storage method.");
datastore = new H2Datastore(plugin, new File(plugin.getDataFolder(), "luckperms.db"));
break;
case "mongodb":
plugin.getLog().info("Using MongoDB as storage method.");
datastore = new MongoDBDatastore(plugin, plugin.getConfiguration().getDatabaseValues());
break;
default:
plugin.getLog().info("Using Flatfile (JSON) as storage method.");
datastore = new FlatfileDatastore(plugin, plugin.getDataFolder());
break;
}
plugin.getLog().info("Initialising datastore...");
datastore.init();
return datastore;
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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 java.io.File;
import java.sql.*;
public class H2Datastore extends SQLDatastore {
private static final String CREATETABLE_UUID = "CREATE TABLE IF NOT EXISTS `lp_uuid` (`name` VARCHAR(16) NOT NULL, `uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS `lp_users` (`uuid` VARCHAR(36) NOT NULL, `name` VARCHAR(16) NOT NULL, `primary_group` VARCHAR(36) NOT NULL, `perms` TEXT NOT NULL, PRIMARY KEY (`uuid`)) DEFAULT CHARSET=utf8;";
private static final String CREATETABLE_GROUPS = "CREATE TABLE IF NOT EXISTS `lp_groups` (`name` VARCHAR(36) NOT NULL, `perms` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INT AUTO_INCREMENT NOT NULL, `time` BIGINT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET=utf8;";
private final File file;
private Connection connection = null;
public H2Datastore(LuckPermsPlugin plugin, File file) {
super(plugin, "H2");
this.file = file;
}
@Override
public void init() {
if (!setupTables(CREATETABLE_UUID, CREATETABLE_USERS, CREATETABLE_GROUPS, CREATETABLE_TRACKS, CREATETABLE_ACTION)) {
plugin.getLog().severe("Error occurred whilst initialising the database.");
shutdown();
} else {
setAcceptingLogins(true);
}
}
@Override
boolean runQuery(QueryPS queryPS) {
boolean success = false;
try {
Connection connection = getConnection();
if (connection == null || connection.isClosed()) {
throw new IllegalStateException("SQL connection is null");
}
@Cleanup PreparedStatement preparedStatement = connection.prepareStatement(queryPS.getQuery());
queryPS.onRun(preparedStatement);
preparedStatement.execute();
success = true;
} catch (SQLException e) {
e.printStackTrace();
}
return success;
}
@Override
boolean runQuery(QueryRS queryRS) {
boolean success = false;
try {
Connection connection = getConnection();
if (connection == null || connection.isClosed()) {
throw new IllegalStateException("SQL connection is null");
}
@Cleanup PreparedStatement preparedStatement = connection.prepareStatement(queryRS.getQuery());
queryRS.onRun(preparedStatement);
preparedStatement.execute();
@Cleanup ResultSet resultSet = preparedStatement.executeQuery();
success = queryRS.onResult(resultSet);
} catch (SQLException e) {
e.printStackTrace();
}
return success;
}
@Override
public void shutdown() {
try {
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
Connection getConnection() throws SQLException {
if (connection == null || connection.isClosed()) {
try {
Class.forName("org.h2.Driver");
} catch (ClassNotFoundException ignored) {}
connection = DriverManager.getConnection("jdbc:h2:" + file.getAbsolutePath());
}
return connection;
}
}

View File

@ -0,0 +1,453 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.InsertOneOptions;
import me.lucko.luckperms.LuckPermsPlugin;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.data.Log;
import me.lucko.luckperms.groups.Group;
import me.lucko.luckperms.groups.GroupManager;
import me.lucko.luckperms.storage.Datastore;
import me.lucko.luckperms.storage.DatastoreConfiguration;
import me.lucko.luckperms.tracks.Track;
import me.lucko.luckperms.tracks.TrackManager;
import me.lucko.luckperms.users.User;
import org.bson.Document;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
@SuppressWarnings("unchecked")
public class MongoDBDatastore extends Datastore {
private final DatastoreConfiguration configuration;
private MongoClient mongoClient;
private MongoDatabase database;
public MongoDBDatastore(LuckPermsPlugin plugin, DatastoreConfiguration configuration) {
super(plugin, "MongoDB");
this.configuration = configuration;
}
@Override
public void init() {
MongoCredential credential = MongoCredential.createCredential(
configuration.getUsername(),
configuration.getDatabase(),
configuration.getPassword().toCharArray()
);
ServerAddress address = new ServerAddress(
configuration.getAddress().split(":")[0],
Integer.parseInt(configuration.getAddress().split(":")[1])
);
mongoClient = new MongoClient(address, Collections.singletonList(credential));
database = mongoClient.getDatabase(configuration.getDatabase());
setAcceptingLogins(true);
}
@Override
public void shutdown() {
if (mongoClient != null) {
mongoClient.close();
}
}
@Override
public boolean logAction(LogEntry entry) {
return call(() -> {
MongoCollection<Document> c = database.getCollection("action");
Document doc = new Document()
.append("timestamp", entry.getTimestamp())
.append("actor", entry.getActor())
.append("actorName", entry.getActorName())
.append("type", Character.toString(entry.getType()))
.append("actedName", entry.getActedName())
.append("action", entry.getAction());
if (entry.getActed() != null) {
doc.append("acted", entry.getActed());
}
c.insertOne(doc, new InsertOneOptions());
return true;
}, false);
}
@Override
public Log getLog() {
return call(() -> {
final Log.Builder log = Log.builder();
MongoCollection<Document> c = database.getCollection("action");
try (MongoCursor<Document> cursor = c.find().iterator()) {
while (cursor.hasNext()) {
Document d = cursor.next();
UUID actedUuid = null;
if (d.containsKey("acted")) {
actedUuid = d.get("acted", UUID.class);
}
LogEntry e = new LogEntry(
d.getLong("timestamp"),
d.get("actor", UUID.class),
d.getString("actorName"),
d.getString("type").toCharArray()[0],
actedUuid,
d.getString("actedName"),
d.getString("action")
);
log.add(e);
}
}
return log.build();
}, null);
}
@Override
public boolean loadOrCreateUser(UUID uuid, String username) {
User user = plugin.getUserManager().make(uuid, username);
boolean success = call(() -> {
MongoCollection<Document> c = database.getCollection("users");
try (MongoCursor<Document> cursor = c.find(new Document("_id", user.getUuid())).iterator()) {
if (!cursor.hasNext()) {
plugin.getUserManager().giveDefaults(user);
c.insertOne(fromUser(user));
} else {
Document d = cursor.next();
user.setPrimaryGroup(d.getString("primaryGroup"));
user.setNodes(revert((Map<String, Boolean>) d.get("perms")));
if (!d.getString("name").equals(user.getName())) {
c.replaceOne(new Document("_id", user.getUuid()), fromUser(user));
}
}
}
return true;
}, false);
if (success) plugin.getUserManager().updateOrSet(user);
return success;
}
@Override
public boolean loadUser(UUID uuid) {
User user = plugin.getUserManager().make(uuid);
boolean success = call(() -> {
MongoCollection<Document> c = database.getCollection("users");
try (MongoCursor<Document> cursor = c.find(new Document("_id", user.getUuid())).iterator()) {
if (cursor.hasNext()) {
Document d = cursor.next();
user.setName(d.getString("name"));
user.setPrimaryGroup(d.getString("primaryGroup"));
user.setNodes(revert((Map<String, Boolean>) d.get("perms")));
return true;
}
return false;
}
}, false);
if (success) plugin.getUserManager().updateOrSet(user);
return success;
}
@Override
public boolean saveUser(User user) {
return call(() -> {
MongoCollection<Document> c = database.getCollection("users");
c.replaceOne(new Document("_id", user.getUuid()), fromUser(user));
return true;
}, false);
}
@Override
public boolean createAndLoadGroup(String name) {
Group group = plugin.getGroupManager().make(name);
boolean success = call(() -> {
MongoCollection<Document> c = database.getCollection("groups");
try (MongoCursor<Document> cursor = c.find(new Document("_id", group.getName())).iterator()) {
if (!cursor.hasNext()) {
c.insertOne(fromGroup(group));
} else {
Document d = cursor.next();
group.setNodes(revert((Map<String, Boolean>) d.get("perms")));
}
}
return true;
}, false);
if (success) plugin.getGroupManager().updateOrSet(group);
return success;
}
@Override
public boolean loadGroup(String name) {
Group group = plugin.getGroupManager().make(name);
boolean success = call(() -> {
MongoCollection<Document> c = database.getCollection("groups");
try (MongoCursor<Document> cursor = c.find(new Document("_id", group.getName())).iterator()) {
if (cursor.hasNext()) {
Document d = cursor.next();
group.setNodes(revert((Map<String, Boolean>) d.get("perms")));
return true;
}
return false;
}
}, false);
if (success) plugin.getGroupManager().updateOrSet(group);
return success;
}
@Override
public boolean loadAllGroups() {
List<Group> groups = new ArrayList<>();
boolean success = call(() -> {
MongoCollection<Document> c = database.getCollection("groups");
try (MongoCursor<Document> cursor = c.find().iterator()) {
while (cursor.hasNext()) {
Document d = cursor.next();
Group group = plugin.getGroupManager().make(d.getString("_id"));
group.setNodes(revert((Map<String, Boolean>) d.get("perms")));
groups.add(group);
}
}
return true;
}, false);
if (success) {
GroupManager gm = plugin.getGroupManager();
gm.unloadAll();
groups.forEach(gm::set);
}
return success;
}
@Override
public boolean saveGroup(Group group) {
return call(() -> {
MongoCollection<Document> c = database.getCollection("groups");
return c.replaceOne(new Document("_id", group.getName()), fromGroup(group)).wasAcknowledged();
}, false);
}
@Override
public boolean deleteGroup(Group group) {
boolean success = call(() -> {
MongoCollection<Document> c = database.getCollection("groups");
return c.deleteOne(new Document("_id", group.getName())).wasAcknowledged();
}, false);
if (success) plugin.getGroupManager().unload(group);
return success;
}
@Override
public boolean createAndLoadTrack(String name) {
Track track = plugin.getTrackManager().make(name);
boolean success = call(() -> {
MongoCollection<Document> c = database.getCollection("tracks");
try (MongoCursor<Document> cursor = c.find(new Document("_id", track.getName())).iterator()) {
if (!cursor.hasNext()) {
c.insertOne(fromTrack(track));
} else {
Document d = cursor.next();
track.setGroups((List<String>) d.get("groups"));
}
}
return true;
}, false);
if (success) plugin.getTrackManager().updateOrSet(track);
return success;
}
@Override
public boolean loadTrack(String name) {
Track track = plugin.getTrackManager().make(name);
boolean success = call(() -> {
MongoCollection<Document> c = database.getCollection("tracks");
try (MongoCursor<Document> cursor = c.find(new Document("_id", track.getName())).iterator()) {
if (cursor.hasNext()) {
Document d = cursor.next();
track.setGroups((List<String>) d.get("groups"));
return true;
}
return false;
}
}, false);
if (success) plugin.getTrackManager().updateOrSet(track);
return success;
}
@Override
public boolean loadAllTracks() {
List<Track> tracks = new ArrayList<>();
boolean success = call(() -> {
MongoCollection<Document> c = database.getCollection("tracks");
try (MongoCursor<Document> cursor = c.find().iterator()) {
while (cursor.hasNext()) {
Document d = cursor.next();
Track track = plugin.getTrackManager().make(d.getString("_id"));
track.setGroups((List<String>) d.get("groups"));
tracks.add(track);
}
}
return true;
}, false);
if (success) {
TrackManager tm = plugin.getTrackManager();
tm.unloadAll();
tracks.forEach(tm::set);
}
return success;
}
@Override
public boolean saveTrack(Track track) {
return call(() -> {
MongoCollection<Document> c = database.getCollection("tracks");
return c.replaceOne(new Document("_id", track.getName()), fromTrack(track)).wasAcknowledged();
}, false);
}
@Override
public boolean deleteTrack(Track track) {
boolean success = call(() -> {
MongoCollection<Document> c = database.getCollection("tracks");
return c.deleteOne(new Document("_id", track.getName())).wasAcknowledged();
}, false);
if (success) plugin.getTrackManager().unload(track);
return success;
}
@Override
public boolean saveUUIDData(String username, UUID uuid) {
return call(() -> {
MongoCollection<Document> c = database.getCollection("uuid");
try (MongoCursor<Document> cursor = c.find(new Document("_id", uuid)).iterator()) {
if (cursor.hasNext()) {
c.replaceOne(new Document("_id", uuid), new Document("_id", uuid).append("name", username.toLowerCase()));
} else {
c.insertOne(new Document("_id", uuid).append("name", username.toLowerCase()));
}
}
return true;
}, false);
}
@Override
public UUID getUUID(String username) {
return call(() -> {
MongoCollection<Document> c = database.getCollection("uuid");
try (MongoCursor<Document> cursor = c.find(new Document("name", username.toLowerCase())).iterator()) {
if (cursor.hasNext()) {
return cursor.next().get("_id", UUID.class);
}
}
return null;
}, null);
}
private static <T> T call(Callable<T> c, T def) {
try {
return c.call();
} catch (Exception e) {
e.printStackTrace();
return def;
}
}
/* MongoDB does not allow '.' or '$' in key names.
See: https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
The following two methods convert the node maps so they can be stored. */
private static <V> Map<String, V> convert(Map<String, V> map) {
return map.entrySet().stream()
.map(e -> new AbstractMap.SimpleEntry<>(e.getKey().replace(".", "[**DOT**]").replace("$", "[**DOLLAR**]"), e.getValue()))
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
}
private static <V> Map<String, V> revert(Map<String, V> map) {
return map.entrySet().stream()
.map(e -> new AbstractMap.SimpleEntry<>(e.getKey().replace("[**DOT**]", ".").replace("[**DOLLAR**]", "$"), e.getValue()))
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
}
private static Document fromUser(User user) {
Document main = new Document("_id", user.getUuid())
.append("name", user.getName())
.append("primaryGroup", user.getPrimaryGroup());
Document perms = new Document();
for (Map.Entry<String, Boolean> e : convert(user.getNodes()).entrySet()) {
perms.append(e.getKey(), e.getValue());
}
main.append("perms", perms);
return main;
}
private static Document fromGroup(Group group) {
Document main = new Document("_id", group.getName());
Document perms = new Document();
for (Map.Entry<String, Boolean> e : convert(group.getNodes()).entrySet()) {
perms.append(e.getKey(), e.getValue());
}
main.append("perms", perms);
return main;
}
private static Document fromTrack(Track track) {
return new Document("_id", track.getName()).append("groups", track.getGroups());
}
}

View File

@ -25,7 +25,7 @@ package me.lucko.luckperms.storage.methods;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Cleanup;
import me.lucko.luckperms.LuckPermsPlugin;
import me.lucko.luckperms.storage.MySQLConfiguration;
import me.lucko.luckperms.storage.DatastoreConfiguration;
import java.sql.Connection;
import java.sql.PreparedStatement;
@ -40,10 +40,10 @@ public class MySQLDatastore extends SQLDatastore {
private static final String CREATETABLE_TRACKS = "CREATE TABLE IF NOT EXISTS `lp_tracks` (`name` VARCHAR(36) NOT NULL, `groups` TEXT NULL, PRIMARY KEY (`name`)) DEFAULT CHARSET=utf8;";
private static final String CREATETABLE_ACTION = "CREATE TABLE IF NOT EXISTS `lp_actions` (`id` INT AUTO_INCREMENT NOT NULL, `time` BIGINT NOT NULL, `actor_uuid` VARCHAR(36) NOT NULL, `actor_name` VARCHAR(16) NOT NULL, `type` CHAR(1) NOT NULL, `acted_uuid` VARCHAR(36) NOT NULL, `acted_name` VARCHAR(36) NOT NULL, `action` VARCHAR(256) NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET=utf8;";
private final MySQLConfiguration configuration;
private final DatastoreConfiguration configuration;
private HikariDataSource hikari;
public MySQLDatastore(LuckPermsPlugin plugin, MySQLConfiguration configuration) {
public MySQLDatastore(LuckPermsPlugin plugin, DatastoreConfiguration configuration) {
super(plugin, "MySQL");
this.configuration = configuration;
}

View File

@ -109,7 +109,7 @@ abstract class SQLDatastore extends Datastore {
boolean onResult(ResultSet resultSet) throws SQLException {
if (resultSet.next()) {
user.setName(resultSet.getString("name"));
user.getNodes().putAll(gson.fromJson(resultSet.getString("perms"), NM_TYPE));
user.setNodes(gson.fromJson(resultSet.getString("perms"), NM_TYPE));
user.setPrimaryGroup(resultSet.getString("primary_group"));
return true;
}
@ -193,7 +193,7 @@ abstract class SQLDatastore extends Datastore {
}
});
} else {
user.getNodes().putAll(gson.fromJson(resultSet.getString("perms"), NM_TYPE));
user.setNodes(gson.fromJson(resultSet.getString("perms"), NM_TYPE));
user.setPrimaryGroup(resultSet.getString("primary_group"));
if (!resultSet.getString("name").equals(user.getName())) {
@ -251,7 +251,7 @@ abstract class SQLDatastore extends Datastore {
}
});
} else {
group.getNodes().putAll(gson.fromJson(resultSet.getString("perms"), NM_TYPE));
group.setNodes(gson.fromJson(resultSet.getString("perms"), NM_TYPE));
}
return success;
}
@ -273,7 +273,7 @@ abstract class SQLDatastore extends Datastore {
@Override
boolean onResult(ResultSet resultSet) throws SQLException {
if (resultSet.next()) {
group.getNodes().putAll(gson.fromJson(resultSet.getString("perms"), NM_TYPE));
group.setNodes(gson.fromJson(resultSet.getString("perms"), NM_TYPE));
return true;
}
return false;
@ -297,7 +297,7 @@ abstract class SQLDatastore extends Datastore {
boolean onResult(ResultSet resultSet) throws SQLException {
while (resultSet.next()) {
Group group = plugin.getGroupManager().make(resultSet.getString("name"));
group.getNodes().putAll(gson.fromJson(resultSet.getString("perms"), NM_TYPE));
group.setNodes(gson.fromJson(resultSet.getString("perms"), NM_TYPE));
groups.add(group);
}
return true;

View File

@ -6,7 +6,7 @@
<groupId>me.lucko.luckperms</groupId>
<artifactId>luckperms</artifactId>
<version>2.3</version>
<version>2.4</version>
<modules>
<module>common</module>
<module>api</module>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>2.3</version>
<version>2.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -43,12 +43,27 @@
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<minimizeJar>false</minimizeJar>
<artifactSet>
<excludes>
<exclude>org.slf4j:*</exclude>
<exclude>org.xerial:*</exclude>
<exclude>com.h2database:*</exclude>
</excludes>
</artifactSet>
<relocations>
<relocation>
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>me.lucko.luckperms.lib.hikari</shadedPattern>
</relocation>
<relocation>
<pattern>com.mongodb</pattern>
<shadedPattern>me.lucko.luckperms.lib.mongodb</shadedPattern>
</relocation>
<relocation>
<pattern>org.bson</pattern>
<shadedPattern>me.lucko.luckperms.lib.bson</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
@ -128,13 +143,6 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.4.7</version>
<scope>compile</scope>
</dependency>
<!-- javassist maven -->
<dependency>
<groupId>de.icongmbh.oss.maven.plugins</groupId>

View File

@ -35,9 +35,7 @@ import me.lucko.luckperms.data.Importer;
import me.lucko.luckperms.groups.GroupManager;
import me.lucko.luckperms.runnables.UpdateTask;
import me.lucko.luckperms.storage.Datastore;
import me.lucko.luckperms.storage.methods.FlatfileDatastore;
import me.lucko.luckperms.storage.methods.MySQLDatastore;
import me.lucko.luckperms.storage.methods.SQLiteDatastore;
import me.lucko.luckperms.storage.StorageFactory;
import me.lucko.luckperms.tracks.TrackManager;
import me.lucko.luckperms.users.SpongeUserManager;
import me.lucko.luckperms.users.UserManager;
@ -106,24 +104,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
SpongeCommand commandManager = new SpongeCommand(this);
cmdService.register(this, commandManager, "luckperms", "perms", "lp", "permissions", "p", "perm");
getLog().info("Detecting storage method...");
final String storageMethod = configuration.getStorageMethod();
if (storageMethod.equalsIgnoreCase("mysql")) {
getLog().info("Using MySQL as storage method.");
datastore = new MySQLDatastore(this, configuration.getDatabaseValues());
} else if (storageMethod.equalsIgnoreCase("sqlite")) {
getLog().info("Using SQLite as storage method.");
datastore = new SQLiteDatastore(this, new File(getMainDir(), "luckperms.sqlite"));
} else if (storageMethod.equalsIgnoreCase("flatfile")) {
getLog().info("Using Flatfile (JSON) as storage method.");
datastore = new FlatfileDatastore(this, getMainDir());
} else {
getLog().severe("Storage method '" + storageMethod + "' was not recognised. Using SQLite as fallback.");
datastore = new SQLiteDatastore(this, new File(getMainDir(), "luckperms.sqlite"));
}
getLog().info("Initialising datastore...");
datastore.init();
datastore = StorageFactory.getDatastore(this, "h2");
getLog().info("Loading internal permission managers...");
uuidCache = new UuidCache(getConfiguration().getOnlineMode());
@ -196,6 +177,11 @@ public class LPSpongePlugin implements LuckPermsPlugin {
return luckPermsDir;
}
@Override
public File getDataFolder() {
return getMainDir();
}
@Override
public String getVersion() {
return "null";
@ -221,6 +207,11 @@ public class LPSpongePlugin implements LuckPermsPlugin {
return game.getServer().getOnlinePlayers().stream().map(s -> SpongeSenderFactory.get().wrap(s)).collect(Collectors.toList());
}
@Override
public Sender getConsoleSender() {
return SpongeSenderFactory.get().wrap(game.getServer().getConsole());
}
@Override
public List<String> getPossiblePermissions() {
Optional<PermissionService> p = game.getServiceManager().provide(PermissionService.class);

View File

@ -1,4 +1,9 @@
# LuckPerms Configuration
##############################################################################
# +------------------------------------------------------------------------+ #
# | LuckPerms Configuration | #
# | https://github.com/lucko/LuckPerms | #
# +------------------------------------------------------------------------+ #
##############################################################################
# The name of the server, used for server specific permissions. Set to 'global' to disable.
server="global"
@ -38,14 +43,17 @@ apply-regex=true
apply-shorthand=true
# Which storage method the plugin should use.
# Currently supported: mysql, sqlite, flatfile
# Fill out connection info below if you're using MySQL
storage-method="sqlite"
# Currently supported: mysql, sqlite, h2, flatfile, mongodb
# Fill out connection info below if you're using MySQL or MongoDB
storage-method="h2"
sql: {
data: {
address="localhost:3306"
database="minecraft"
username="root"
password=""
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes=3
}