diff --git a/build.gradle b/build.gradle index 9f7197b4..a5684909 100644 --- a/build.gradle +++ b/build.gradle @@ -56,6 +56,8 @@ shadowJar { relocate("net.kyori", "me.clip.voteparty.libs.kyori") relocate("com.cryptomorin.xseries", "me.clip.voteparty.libs.xseries") relocate("kotlin", "me.clip.voteparty.libs.kotlin") + relocate("co.aikar.idb", "me.clip.voteparty.libs.idb") + relocate("com.zaxxer", "me.clip.voteparty.libs.hikari") archiveFileName = "VoteParty-${project.version}.jar" } @@ -82,6 +84,10 @@ dependencies { implementation 'net.kyori:adventure-api:4.11.0' implementation 'net.kyori:adventure-text-minimessage:4.11.0' + // MySQL? + implementation "co.aikar:idb-core:1.0.0-SNAPSHOT" + implementation "com.zaxxer:HikariCP:4.0.3" + implementation project(":version") implementation project(":version_old") implementation project(":version_new") diff --git a/src/main/kotlin/me/clip/voteparty/conf/VotePartyConfiguration.kt b/src/main/kotlin/me/clip/voteparty/conf/VotePartyConfiguration.kt index 900f11e7..5a7f6c27 100644 --- a/src/main/kotlin/me/clip/voteparty/conf/VotePartyConfiguration.kt +++ b/src/main/kotlin/me/clip/voteparty/conf/VotePartyConfiguration.kt @@ -9,24 +9,26 @@ import me.clip.voteparty.conf.sections.EffectsSettings import me.clip.voteparty.conf.sections.HookSettings import me.clip.voteparty.conf.sections.PartySettings import me.clip.voteparty.conf.sections.PluginSettings +import me.clip.voteparty.conf.sections.StorageSettings import me.clip.voteparty.conf.sections.VoteSettings import java.io.File internal class VotePartyConfiguration(file: File) : SettingsManagerImpl(YamlFileResource(file.toPath()), ConfigurationDataBuilder.createConfiguration(SECTIONS), VoteMigrationService()) { - + private companion object { - + private val SECTIONS = listOf( PluginSettings::class.java, + StorageSettings::class.java, HookSettings::class.java, CrateSettings::class.java, EffectsSettings::class.java, PartySettings::class.java, VoteSettings::class.java ) - + } - -} \ No newline at end of file + +} diff --git a/src/main/kotlin/me/clip/voteparty/conf/objects/SQLData.kt b/src/main/kotlin/me/clip/voteparty/conf/objects/SQLData.kt new file mode 100644 index 00000000..e7133d70 --- /dev/null +++ b/src/main/kotlin/me/clip/voteparty/conf/objects/SQLData.kt @@ -0,0 +1,6 @@ +package me.clip.voteparty.conf.objects + +internal data class SQLData(var user: String = "username", + var password: String = "password", + var database: String = "database", + var host: String = "host") diff --git a/src/main/kotlin/me/clip/voteparty/conf/sections/StorageSettings.kt b/src/main/kotlin/me/clip/voteparty/conf/sections/StorageSettings.kt new file mode 100644 index 00000000..04b1288b --- /dev/null +++ b/src/main/kotlin/me/clip/voteparty/conf/sections/StorageSettings.kt @@ -0,0 +1,18 @@ +package me.clip.voteparty.conf.sections + +import ch.jalu.configme.Comment +import ch.jalu.configme.SettingsHolder +import ch.jalu.configme.properties.Property +import ch.jalu.configme.properties.PropertyInitializer.newBeanProperty +import ch.jalu.configme.properties.PropertyInitializer.newProperty +import me.clip.voteparty.conf.objects.SQLData + +internal object StorageSettings : SettingsHolder { + + @JvmField + @Comment("What storage backend would you like to use? We currently support JSON & MySQL") + val BACKEND: Property = newProperty("storage.backend", "JSON") + + @JvmField + val SQL : Property = newBeanProperty(SQLData::class.java, "storage.mysql_settings", SQLData("user", "pass", "database", "localhost")) +} diff --git a/src/main/kotlin/me/clip/voteparty/data/base/DatabaseVotePlayer.kt b/src/main/kotlin/me/clip/voteparty/data/base/DatabaseVotePlayer.kt index 9890cddf..7d6b7dc4 100644 --- a/src/main/kotlin/me/clip/voteparty/data/base/DatabaseVotePlayer.kt +++ b/src/main/kotlin/me/clip/voteparty/data/base/DatabaseVotePlayer.kt @@ -7,25 +7,27 @@ import java.util.UUID internal interface DatabaseVotePlayer : Addon, State { - + override fun load() - + override fun kill() - - + + fun load(uuid: UUID): User? - + fun save(data: User) - - + + fun load(uuid: Collection): Map { return uuid.associateWith(::load) } - + fun save(data: Collection) { data.forEach(::save) } - -} \ No newline at end of file + + fun reset(data: User) + +} diff --git a/src/main/kotlin/me/clip/voteparty/data/impl/DatabaseVotePlayerGson.kt b/src/main/kotlin/me/clip/voteparty/data/impl/DatabaseVotePlayerGson.kt index cf5f6c94..a426c8c9 100644 --- a/src/main/kotlin/me/clip/voteparty/data/impl/DatabaseVotePlayerGson.kt +++ b/src/main/kotlin/me/clip/voteparty/data/impl/DatabaseVotePlayerGson.kt @@ -10,24 +10,24 @@ import java.util.logging.Level internal class DatabaseVotePlayerGson(override val plugin: VotePartyPlugin) : DatabaseVotePlayer { - + private lateinit var gson: Gson - - + + override fun load() { val builder = GsonBuilder().disableHtmlEscaping().enableComplexMapKeySerialization().setPrettyPrinting().serializeNulls() gson = builder.create() - + plugin.dataFolder.resolve("players").mkdirs() } - + override fun kill() { - + } - - + + override fun load(uuid: UUID): User? { return try @@ -40,7 +40,7 @@ internal class DatabaseVotePlayerGson(override val plugin: VotePartyPlugin) : Da null } } - + override fun save(data: User) { try @@ -52,16 +52,21 @@ internal class DatabaseVotePlayerGson(override val plugin: VotePartyPlugin) : Da logger.log(Level.SEVERE, "failed to save player:${data.uuid}", ex) } } - + + override fun reset(data: User) + { + data.reset() + } + override fun load(uuid: Collection): Map { if (uuid.isNotEmpty()) { return super.load(uuid) } - + val files = plugin.dataFolder.resolve("players").listFiles() ?: return emptyMap() - + return files.mapNotNull() { try @@ -74,5 +79,5 @@ internal class DatabaseVotePlayerGson(override val plugin: VotePartyPlugin) : Da } }.associateWith(::load) } - -} \ No newline at end of file + +} diff --git a/src/main/kotlin/me/clip/voteparty/data/impl/DatabaseVotePlayerMySQL.kt b/src/main/kotlin/me/clip/voteparty/data/impl/DatabaseVotePlayerMySQL.kt new file mode 100644 index 00000000..4139c699 --- /dev/null +++ b/src/main/kotlin/me/clip/voteparty/data/impl/DatabaseVotePlayerMySQL.kt @@ -0,0 +1,116 @@ +package me.clip.voteparty.data.impl + +import co.aikar.idb.DB +import co.aikar.idb.Database +import co.aikar.idb.DatabaseOptions +import co.aikar.idb.PooledDatabaseOptions +import me.clip.voteparty.conf.sections.StorageSettings +import me.clip.voteparty.data.base.DatabaseVotePlayer +import me.clip.voteparty.plugin.VotePartyPlugin +import me.clip.voteparty.user.User +import java.math.BigInteger +import java.sql.SQLException +import java.util.UUID +import java.util.logging.Level + +internal class DatabaseVotePlayerMySQL(override val plugin: VotePartyPlugin) : DatabaseVotePlayer +{ + + private lateinit var database: Database + + override fun load() + { + val sql = party.conf().getProperty(StorageSettings.SQL) + + val options = DatabaseOptions.builder() + .mysql(sql.user, sql.password, sql.database, sql.host) + .logger(plugin.logger) + .poolName("VoteParty Database") + .build() + + database = PooledDatabaseOptions.builder().options(options).createHikariDatabase() + + DB.setGlobalDatabase(database) + + try + { + database.executeUpdate( + "CREATE TABLE IF NOT EXISTS `voteparty_players` (" + + " `uuid` varchar(255) NOT NULL," + + " `name` varchar(100) NOT NULL," + + " `claimable` int(9) unsigned NOT NULL," + + " PRIMARY KEY (`uuid`)" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;" + ) + + database.executeUpdate( + "CREATE TABLE IF NOT EXISTS `voteparty_votes` (" + + " `timestamp` bigint(20) unsigned NOT NULL," + + " `uuid` varchar(255) NOT NULL," + + " PRIMARY KEY (`timestamp`)" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;" + ) + + } + catch (ex: SQLException) + { + throw RuntimeException("Error while creating database table:", ex) + } + } + + override fun kill() + { + DB.close() + } + + override fun load(uuid: UUID): User? + { + return try + { + val row = database.getFirstRow("SELECT name, claimable FROM voteparty_players WHERE uuid = ?", uuid.toString()) + val user = User(uuid, row.getString("name"), mutableListOf(), row.getInt("claimable")) + + database.getResults("SELECT timestamp FROM voteparty_votes WHERE uuid = ?", uuid.toString()).forEach { vote -> + val stamp : BigInteger = vote.get("timestamp") + user.voted(stamp.toLong()) + } + user + } + catch (ex: SQLException) + { + logger.log(Level.SEVERE, "failed to load player:$uuid", ex) + null + } + } + + override fun save(data: User) + { + database.executeInsert("INSERT INTO voteparty_players (uuid, name, claimable) VALUES(?, ?, ?) ON DUPLICATE " + + "KEY UPDATE name = ?, claimable = ?", data.uuid.toString(), data.name, data.claimable, data.name, data.claimable) + + data.votes().forEach { vote -> + database.executeInsert("INSERT IGNORE INTO voteparty_votes (timestamp, uuid) VALUES(?, ?)", vote, data.uuid.toString()) + } + } + + override fun reset(data: User) + { + database.executeUpdate("DELETE FROM voteparty_votes WHERE UUID = ?", data.uuid.toString()) + } + + override fun load(uuid: Collection): Map + { + if (uuid.isNotEmpty()) + { + return super.load(uuid) + } + + val data = mutableListOf() + + database.getResults("SELECT uuid from voteparty_players").forEach { results -> + data.add(UUID.fromString(results.getString("uuid"))) + } + + return data.associateWith(::load) + } +} diff --git a/src/main/kotlin/me/clip/voteparty/user/User.kt b/src/main/kotlin/me/clip/voteparty/user/User.kt index f404b8fc..5a0f09a3 100644 --- a/src/main/kotlin/me/clip/voteparty/user/User.kt +++ b/src/main/kotlin/me/clip/voteparty/user/User.kt @@ -6,27 +6,32 @@ import java.util.UUID data class User(val uuid: UUID, var name: String, private val data: MutableList, var claimable: Int) { - + fun voted() { data += System.currentTimeMillis() } - + + fun voted(time: Long) + { + data += time + } + fun votes(): List { return data } - + fun hasVotedBefore(): Boolean { return data.isNotEmpty() } - + fun reset() { data.clear() } - + fun player() : OfflinePlayer { return Bukkit.getOfflinePlayer(uuid) diff --git a/src/main/kotlin/me/clip/voteparty/user/UsersHandler.kt b/src/main/kotlin/me/clip/voteparty/user/UsersHandler.kt index ede01eb0..4996f997 100644 --- a/src/main/kotlin/me/clip/voteparty/user/UsersHandler.kt +++ b/src/main/kotlin/me/clip/voteparty/user/UsersHandler.kt @@ -2,7 +2,10 @@ package me.clip.voteparty.user import me.clip.voteparty.base.Addon import me.clip.voteparty.base.State +import me.clip.voteparty.conf.sections.StorageSettings +import me.clip.voteparty.data.base.DatabaseVotePlayer import me.clip.voteparty.data.impl.DatabaseVotePlayerGson +import me.clip.voteparty.data.impl.DatabaseVotePlayerMySQL import me.clip.voteparty.leaderboard.LeaderboardUser import me.clip.voteparty.plugin.VotePartyPlugin import org.bukkit.OfflinePlayer @@ -24,12 +27,18 @@ import java.util.concurrent.TimeUnit class UsersHandler(override val plugin: VotePartyPlugin) : Addon, State, Listener { - private val database = DatabaseVotePlayerGson(plugin) + private lateinit var database : DatabaseVotePlayer private val cached = mutableMapOf() override fun load() { + database = if (party.conf().getProperty(StorageSettings.BACKEND).equals("mysql", true)) { + DatabaseVotePlayerMySQL(plugin) + } else { + DatabaseVotePlayerGson(plugin) + } + database.load() database.load(emptyList()).forEach() @@ -74,7 +83,7 @@ class UsersHandler(override val plugin: VotePartyPlugin) : Addon, State, Listene fun reset(player: OfflinePlayer) { - get(player).reset() + database.reset(get(player)) } fun saveAll()