diff --git a/README.md b/README.md index 34fb17b..624ac3a 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ sqlSettings: database: 'skinfixer_database' username: 'skinfixer_user' password: 'super secure password herer' + port: YOUR_DATABASE_PORT # Should SkinFixer itegrate with Discord, if this is set to true you will need to configure discordSettigns useDiscord: false diff --git a/build.gradle b/build.gradle index a0bd4cd..5ab0b1a 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ dependencies { compileOnly 'org.spigotmc:spigot-api:1.17-R0.1-SNAPSHOT' implementation 'dev.array21:httplib:1.2.2' - implementation 'dev.array21:classvalidator:1.0.0' + implementation 'dev.array21:classvalidator:1.0.1' implementation 'dev.array21:bukkit-reflection-util:1.3.1' // JDA and related dependencies diff --git a/gradle.properties b/gradle.properties index 9d8f2f3..c73bbb0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -pluginVersion = 1.7.9 +pluginVersion = 1.7.10 diff --git a/lib/Cargo.toml b/lib/Cargo.toml index fba5576..16e9c65 100755 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -2,7 +2,7 @@ name = "skinfixer" version = "0.1.0" authors = ["Tobias de Bruijn "] -edition = "2018" +edition = "2021" [lib] name = "skinfixer" @@ -14,26 +14,11 @@ jni = "0.19.0" postgres = "0.19.1" bincode = "1.3.3" lazy_static = "1.4.0" - -[dependencies.mysql] -version = "23.0.0" -default-features = false - -[dependencies.flate2] -version = "1.0.22" -features = ["zlib"] - -[dependencies.rusqlite] -version = "0.25.1" -features = ["bundled"] - -[dependencies.serde] -version = "1.0.126" -features = ["derive"] - -[dependencies.typenum] -version = "1.14.0" -features = ["no_std"] +sqlx = { version = "0.7.2", features = ["any", "mysql", "postgres", "sqlite", "runtime-tokio-rustls"] } +serde = { version = "1.0.188", features = ["derive"] } +typenum = { version = "1.17.0", features = ["no_std"] } +tokio = { version = "1.33.0", features = ["fs", "rt", "rt-multi-thread"] } +thiserror = "1.0.49" [profile.release] lto = true diff --git a/lib/src/config.rs b/lib/src/config.rs deleted file mode 100755 index 9c6009d..0000000 --- a/lib/src/config.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! Module containing all things related to config - -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; -use std::cell::RefCell; -use std::str::FromStr; - -lazy_static::lazy_static! { - /// Configuration instance - /// This is static because of JNI we do not have a nice clean way of keeping an instance around without going static - pub static ref CONFIG: Arc>>> = Arc::new(Mutex::new(RefCell::new(None))); -} - -/// Unwrap an Option to a String, but panic! if the Option is None -macro_rules! unwrap_str { - ($expression:expr, $name:literal) => { - match $expression { - Some(e) => e, - None => panic!("Config variable '{}' is not allowed to be None.", $name) - } - } -} - -/// Struct describing the configuration -pub struct Config { - /// The type of storage being being used - storage_type: StorageType, - /// SQL: host - host: Option, - /// SQL: database - database: Option, - /// SQL: username - username: Option, - /// SQL: password - password: Option, - /// Path to plugin folder - storage_path: Option, - /// MySQL connection pool - pool: Option -} - -impl Config { - /// Get the Path to the Plugin's data folder - /// - /// # Errors - /// Err when self.storage_path is None - pub fn get_path(&self) -> Result<&Path, String> { - match &self.storage_path { - Some(p) => Ok(Path::new(p)), - None=> Err("File storage/SQLite is not used as storage backend.".to_string()) - } - } - - /// Get the type of storage backend being used - pub fn get_type(&self) -> &StorageType { - &self.storage_type - } - - /// Get a MySQL database connection - /// - /// # Errors - /// Err when getting a connection from the pool failed, or if MySQL is not used as storage backend - pub fn mysql_conn(&self) -> Result { - let pool = match &self.pool { - Some(p) => p, - None => return Err("MySQL is not being used as storage backend, thus no Pool is available.".to_string()) - }; - - match pool.get_conn() { - Ok(c) => Ok(c), - Err(e) => Err(format!("Failed to create PooledConn: {:?}", e)) - } - } - - /// Get a PostgreSQL database connection - /// - /// # Errors - /// Err when connecting to the database failed, or when PostgreSQL is not used as database backend - pub fn postgres_conn(&self) -> Result { - let uri = format!("postgresql://{}:{}@{}/{}", unwrap_str!(&self.username, "sqlSettings.username"), unwrap_str!(&self.password, "sqlSettings.password"), unwrap_str!(&self.host, "sqlSettings.host"), unwrap_str!(&self.database, "sqlSettings.database")); - match postgres::Client::connect(&uri, postgres::NoTls) { - Ok(c) => Ok(c), - Err(e) => Err(format!("Failed to create PostgreSQL connection: {:?}", e)) - } - } - - /// Get a SQLite database connection - /// - /// # Errors - /// Err if SQLite is not being used as storage backend, or opening the connection failed - pub fn sqlite_conn(&self) -> Result { - let mut path = PathBuf::from(match &self.storage_path { - Some(p) => p, - None => return Err("SQLite is not being used as storage backend.".to_string()) - }); - - path.push("skins.db3"); - match rusqlite::Connection::open(&path) { - Ok(c) => Ok(c), - Err(e) => Err(e.to_string()) - } - } - - /// Create a new instance of Config - pub fn new(storage_type: StorageType, host: Option, database: Option, username: Option, password: Option, storage_path: Option) -> Self { - let pool = match storage_type { - StorageType::Mysql => { - let opts = mysql::OptsBuilder::new() - .db_name(Some(unwrap_str!(&database, "sqlSettings.database"))) - .ip_or_hostname(Some(unwrap_str!(&host, "sqlSettings.host"))) - .user(Some(unwrap_str!(&username, "sqlSettings.username"))) - .pass(Some(unwrap_str!(&password, "sqlSettings.password"))); - - let pool = match mysql::Pool::new(opts) { - Ok(p) => p, - Err(e) => panic!("Failed to create MySQL Pool: {:?}", e) - }; - - Some(pool) - }, - _ => None - }; - - Self { - storage_type, - host, - database, - username, - password, - storage_path, - pool - } - } -} - -/// The type of storage backend being used -pub enum StorageType { - /// MySQL Database - Mysql, - /// PostgreSQL database - Postgres, - /// Binary format - Bin, - /// SQLite database - Sqlite -} - -impl FromStr for StorageType { - type Err = (); - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "mysql" => Ok(StorageType::Mysql), - "postgres" => Ok(StorageType::Postgres), - "bin" => Ok(StorageType::Bin), - "sqlite" => Ok(StorageType::Sqlite), - _ => Err(()) - } - } -} \ No newline at end of file diff --git a/lib/src/database/mod.rs b/lib/src/database/mod.rs new file mode 100644 index 0000000..fe02de8 --- /dev/null +++ b/lib/src/database/mod.rs @@ -0,0 +1,132 @@ +use crate::database::profile::BinProfile; +use serde::de::DeserializeOwned; +use serde::Serialize; +use sqlx::mysql::MySqlConnectOptions; +use sqlx::postgres::PgConnectOptions; +use sqlx::sqlite::SqliteConnectOptions; +use sqlx::{MySqlPool, PgPool, SqlitePool}; +use std::path::{Path, PathBuf}; +use thiserror::Error; +use tokio::fs; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +mod profile; +pub use profile::*; + +#[derive(Debug, Error)] +pub enum DatabaseError { + #[error("{0}")] + Sql(#[from] sqlx::Error), + #[error("{0}")] + Bin(#[from] BinError), +} + +#[derive(Debug)] +pub enum Driver { + Mysql(MySqlPool), + Postgres(PgPool), + Sqlite(SqlitePool), + Bin(PathBuf), +} + +pub enum DriverType<'a> { + Mysql(DatabaseOptions<'a>), + Postgres(DatabaseOptions<'a>), + Sqlite(PathBuf), + Bin(PathBuf), +} + +pub struct DatabaseOptions<'a> { + pub name: &'a str, + pub user: &'a str, + pub passw: &'a str, + pub host: &'a str, + pub port: u16, +} + +impl Driver { + pub async fn new(driver_type: DriverType<'_>) -> Result { + let driver = match driver_type { + DriverType::Mysql(c) => Self::new_mysql(c).await?, + DriverType::Postgres(c) => Self::new_postgres(c).await?, + DriverType::Sqlite(c) => Self::new_sqlite(&c).await?, + DriverType::Bin(c) => Self::new_bin(c).await?, + }; + + Ok(driver) + } + + async fn new_mysql(options: DatabaseOptions<'_>) -> Result { + let opts = MySqlConnectOptions::new() + .database(options.name) + .username(options.user) + .password(options.passw) + .host(options.host) + .port(options.port); + + let pool = MySqlPool::connect_with(opts).await?; + Ok(Self::Mysql(pool)) + } + + async fn new_postgres(options: DatabaseOptions<'_>) -> Result { + let opts = PgConnectOptions::new() + .database(options.name) + .username(options.user) + .password(options.passw) + .host(options.host) + .port(options.port); + + let pool = PgPool::connect_with(opts).await?; + Ok(Self::Postgres(pool)) + } + + async fn new_sqlite(path: &Path) -> Result { + let opts = SqliteConnectOptions::new().filename(path); + + let pool = SqlitePool::connect_with(opts).await?; + Ok(Self::Sqlite(pool)) + } + + async fn new_bin(path: PathBuf) -> Result { + if !path.exists() { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).await?; + } + + BinFile::write::>(&path, &vec![]).await?; + } + + Ok(Self::Bin(path)) + } +} + +#[derive(Debug, Error)] +pub enum BinError { + #[error("IO Error: {0}")] + Io(#[from] std::io::Error), + #[error("Binary (de)serialization error: {0}")] + Bin(#[from] bincode::Error), +} + +pub struct BinFile; + +impl BinFile { + async fn read(path: &Path) -> Result { + let mut file = fs::File::open(path).await?; + let mut buf = Vec::new(); + file.read_to_end(&mut buf).await?; + + let deserialized: T = bincode::deserialize(&buf)?; + + Ok(deserialized) + } + + async fn write(path: &Path, content: &T) -> Result<(), BinError> { + let mut file = fs::File::create(path).await?; + let buf = bincode::serialize(content)?; + + file.write_all(&buf).await?; + + Ok(()) + } +} diff --git a/lib/src/database/profile.rs b/lib/src/database/profile.rs new file mode 100644 index 0000000..480b39c --- /dev/null +++ b/lib/src/database/profile.rs @@ -0,0 +1,298 @@ +use crate::database::{BinError, BinFile, DatabaseError, Driver}; +use serde::{Deserialize, Serialize}; +use sqlx::{Error, FromRow, MySqlPool, PgPool, SqlitePool}; +use std::path::Path; + +#[derive(Debug, FromRow)] +pub struct Profile { + pub value: String, + pub signature: String, + pub uuid: String, +} + +#[derive(Deserialize, Serialize)] +pub struct BinProfile { + uuid: String, + value: String, + signature: String, +} + +impl Profile { + pub async fn get_by_uuid(driver: &Driver, uuid: &str) -> Result, DatabaseError> { + let item = match driver { + Driver::Mysql(e) => Self::get_by_uuid_mysql(e, uuid).await?, + Driver::Postgres(e) => Self::get_by_uuid_postgres(e, uuid).await?, + Driver::Sqlite(e) => Self::get_by_uuid_sqlite(e, uuid).await?, + Driver::Bin(p) => Self::get_by_uuid_bin(p, uuid).await?, + }; + + Ok(item) + } + + async fn get_by_uuid_mysql(driver: &MySqlPool, uuid: &str) -> Result, Error> { + sqlx::query_as("SELECT * FROM skins WHERE uuid = ?") + .bind(uuid) + .fetch_optional(driver) + .await + } + + async fn get_by_uuid_postgres(driver: &PgPool, uuid: &str) -> Result, Error> { + sqlx::query_as("SELECT * FROM skins WHERE uuid = $1") + .bind(uuid) + .fetch_optional(driver) + .await + } + + async fn get_by_uuid_sqlite(driver: &SqlitePool, uuid: &str) -> Result, Error> { + sqlx::query_as("SELECT * FROM skins WHERE uuid = ?") + .bind(uuid) + .fetch_optional(driver) + .await + } + + async fn get_by_uuid_bin(path: &Path, uuid: &str) -> Result, BinError> { + let contents: Vec = BinFile::read(path).await?; + Ok(contents + .into_iter() + .find(|f| f.uuid.eq(uuid)) + .map(|f| Self { + uuid: f.uuid, + signature: f.signature, + value: f.value, + })) + } + + pub async fn set_skin_profile( + driver: &Driver, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), DatabaseError> { + if Self::get_by_uuid(driver, uuid).await?.is_some() { + Self::update_skin_profile(driver, uuid, value, signature).await?; + } else { + Self::insert_skin_profile(driver, uuid, value, signature).await?; + } + + Ok(()) + } + + async fn insert_skin_profile( + driver: &Driver, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), DatabaseError> { + match driver { + Driver::Mysql(e) => Self::insert_skin_profile_mysql(e, uuid, value, signature).await?, + Driver::Postgres(e) => { + Self::insert_skin_profile_postgres(e, uuid, value, signature).await? + } + Driver::Sqlite(e) => { + Self::insert_skin_profile_sqlite(e, uuid, value, signature).await? + } + Driver::Bin(p) => Self::insert_skin_profile_bin(p, uuid, value, signature).await?, + } + + Ok(()) + } + + async fn insert_skin_profile_mysql( + driver: &MySqlPool, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), Error> { + sqlx::query("INSERT INTO skins (uuid, value, signature) VALUES (?, ?, ?)") + .bind(uuid) + .bind(value) + .bind(signature) + .execute(driver) + .await?; + Ok(()) + } + + async fn insert_skin_profile_postgres( + driver: &PgPool, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), Error> { + sqlx::query("INSERT INTO skins (uuid, value, signature) VALUES ($1, $2, $3)") + .bind(uuid) + .bind(value) + .bind(signature) + .execute(driver) + .await?; + Ok(()) + } + + async fn insert_skin_profile_sqlite( + driver: &SqlitePool, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), Error> { + sqlx::query("INSERT INTO skins (uuid, value, signature) VALUES (?, ?, ?)") + .bind(uuid) + .bind(value) + .bind(signature) + .execute(driver) + .await?; + Ok(()) + } + + async fn insert_skin_profile_bin( + path: &Path, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), BinError> { + let mut contents: Vec = BinFile::read(path).await?; + contents.push(BinProfile { + uuid: uuid.to_string(), + value: value.to_string(), + signature: signature.to_string(), + }); + + BinFile::write(path, &contents).await?; + + Ok(()) + } + + async fn update_skin_profile( + driver: &Driver, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), DatabaseError> { + match driver { + Driver::Mysql(e) => Self::update_skin_profile_mysql(e, uuid, value, signature).await?, + Driver::Postgres(e) => { + Self::update_skin_profile_postgres(e, uuid, value, signature).await? + } + Driver::Sqlite(e) => { + Self::update_skin_profile_sqlite(e, uuid, value, signature).await? + } + Driver::Bin(p) => Self::update_skin_profile_bin(p, uuid, value, signature).await?, + } + + Ok(()) + } + + async fn update_skin_profile_mysql( + driver: &MySqlPool, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), Error> { + sqlx::query("UPDATE skins SET value = ?, signature = ? WHERE uuid = ?") + .bind(value) + .bind(signature) + .bind(uuid) + .execute(driver) + .await?; + Ok(()) + } + + async fn update_skin_profile_postgres( + driver: &PgPool, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), Error> { + sqlx::query("UPDATE skins SET value = $1, signature = $2 WHERE uuid = $3") + .bind(value) + .bind(signature) + .bind(uuid) + .execute(driver) + .await?; + Ok(()) + } + + async fn update_skin_profile_sqlite( + driver: &SqlitePool, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), Error> { + sqlx::query("UPDATE skins SET value = ?, signature = ? WHERE uuid = ?") + .bind(value) + .bind(signature) + .bind(uuid) + .execute(driver) + .await?; + Ok(()) + } + + async fn update_skin_profile_bin( + path: &Path, + uuid: &str, + value: &str, + signature: &str, + ) -> Result<(), BinError> { + let contents: Vec = BinFile::read(path).await?; + let contents = contents + .into_iter() + .map(|mut f| { + if f.uuid.eq(uuid) { + f.value = value.to_string(); + f.signature = signature.to_string(); + } + + f + }) + .collect::>(); + + BinFile::write(path, &contents).await?; + + Ok(()) + } + + pub async fn delete(driver: &Driver, uuid: &str) -> Result<(), DatabaseError> { + match driver { + Driver::Mysql(e) => Self::delete_mysql(e, uuid).await?, + Driver::Postgres(e) => Self::delete_postgres(e, uuid).await?, + Driver::Sqlite(e) => Self::delete_sqlite(e, uuid).await?, + Driver::Bin(p) => Self::delete_bin(p, uuid).await?, + } + + Ok(()) + } + + async fn delete_mysql(driver: &MySqlPool, uuid: &str) -> Result<(), Error> { + sqlx::query("DELETE FROM skins WHERE uuid = ?") + .bind(uuid) + .execute(driver) + .await?; + Ok(()) + } + + async fn delete_postgres(driver: &PgPool, uuid: &str) -> Result<(), Error> { + sqlx::query("DELETE FROM skins WHERE uuid = $1") + .bind(uuid) + .execute(driver) + .await?; + Ok(()) + } + + async fn delete_sqlite(driver: &SqlitePool, uuid: &str) -> Result<(), Error> { + sqlx::query("DELETE FROM skins WHERE uuid = ?") + .bind(uuid) + .execute(driver) + .await?; + Ok(()) + } + + async fn delete_bin(path: &Path, uuid: &str) -> Result<(), BinError> { + let contents: Vec = BinFile::read(path).await?; + let contents = contents + .into_iter() + .filter(|f| f.uuid.ne(uuid)) + .collect::>(); + + BinFile::write(path, &contents).await?; + + Ok(()) + } +} diff --git a/lib/src/jni/del_skin_profile.rs b/lib/src/jni/del_skin_profile.rs index 6400d66..f6a679b 100755 --- a/lib/src/jni/del_skin_profile.rs +++ b/lib/src/jni/del_skin_profile.rs @@ -1,112 +1,27 @@ -//! JNI bindings for dev.array21.skinfixer.storage.LibSkinFixer#delSkinProfile() - -use crate::config::StorageType; -use crate::jstring_to_string; -use crate::jni::Skin; - -use jni::objects::{JClass, JString}; -use mysql::prelude::Queryable; -use mysql::{Params, params}; -use std::path::PathBuf; -use std::io::Write; -use jni::JNIEnv; -use std::fs; - -/// Java JNI function -/// -/// dev.array21.skinfixer.storage.LibSkinFixer#delSkinProfile(String uuid) -#[no_mangle] -pub extern "system" fn Java_dev_array21_skinfixer_storage_LibSkinFixer_delSkinProfile(env: JNIEnv<'_>, _class: JClass<'_>, uuid: JString<'_>) { - let uuid = jstring_to_string!(env, uuid); - let config_guard = crate::config::CONFIG.lock().expect("Failed to lock CONFIG"); - let config_ref = match config_guard.try_borrow() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - let config = config_ref.as_ref().unwrap(); - - match config.get_type() { - StorageType::Mysql => { - let mut conn = match config.mysql_conn() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - match conn.exec::("DELETE FROM skins WHERE uuid = :uuid", params! { - "uuid" => &uuid - }) { - Ok(_) => {}, - Err(e) => panic!("Failed to remove skin from mysql database: {:?}", e) - }; - }, - StorageType::Postgres => { - let mut conn = match config.postgres_conn() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - match conn.execute("DELETE FROM skins WHERE uuid = $1", &[&uuid]) { - Ok(_) => {}, - Err(e) => panic!("Failed to remove skin from postgre database: {:?}", e) - } - }, - StorageType::Bin => { - let mut path = PathBuf::from(config.get_path().unwrap()); - path.push("skins.bin"); - - let contents = match fs::read(&path) { - Ok(c) => c, - Err(e) => panic!("Failed to open storage bin: {:?}", e) - }; - - if contents.is_empty() { - return; - } - - let skins: Vec = match bincode::deserialize(&contents) { - Ok(s) => s, - Err(e) => panic!("Failed to deserialize storage bin: {:?}", e) - }; - - let skins = skins.into_iter().flat_map(|x| { - if x.uuid.eq(&uuid) { - Err(()) - } else { - Ok(x) - } - }).collect::>(); - - let bytes = match bincode::serialize(&skins) { - Ok(b) => b, - Err(e) => panic!("Failed to deserialize Skins vector into bytes: {:?}", e) - }; - - let mut file = match fs::File::create(&path) { - Ok(f) => f, - Err(e) => panic!("Failed to open storage bin: {:?}", e) - }; - - match file.write_all(&bytes) { - Ok(_) => {}, - Err(e) => panic!("Failed to write to skins.bin file: {:?}", e) - } - }, - StorageType::Sqlite => { - use rusqlite::named_params; - - let conn = match config.sqlite_conn() { - Ok(c) => c, - Err(e) => panic!("Failed to create SQLite connection: {:?}", e) - }; - - let mut stmt = conn.prepare("DELETE FROM skins WHERE uuid = :uuid").unwrap(); - match stmt.execute(named_params! { - ":uuid": uuid - }) { - Ok(_) => {}, - Err(e) => panic!("Failed to delete skin from database: {:?}", e) - } - } - } -} \ No newline at end of file +//! JNI bindings for dev.array21.skinfixer.storage.LibSkinFixer#delSkinProfile() + +use crate::jstring_to_string; + +use crate::database::Profile; +use crate::jni::{DRIVER, TOKIO_RT}; +use jni::objects::{JClass, JString}; +use jni::JNIEnv; + +/// Java JNI function +/// +/// dev.array21.skinfixer.storage.LibSkinFixer#delSkinProfile(String uuid) +#[no_mangle] +pub extern "system" fn Java_dev_array21_skinfixer_storage_LibSkinFixer_delSkinProfile( + env: JNIEnv<'_>, + _class: JClass<'_>, + uuid: JString<'_>, +) { + let uuid = jstring_to_string!(env, uuid); + + let tokio_rt = TOKIO_RT.get().expect("Getting global Tokio runtime"); + let _guard = tokio_rt.enter(); + + let driver = DRIVER.get().expect("Getting global storage driver"); + + let _ = tokio_rt.block_on(Profile::delete(driver, &uuid)); +} diff --git a/lib/src/jni/get_skin_profile.rs b/lib/src/jni/get_skin_profile.rs index 231ee2e..50886cf 100755 --- a/lib/src/jni/get_skin_profile.rs +++ b/lib/src/jni/get_skin_profile.rs @@ -1,162 +1,65 @@ -//! JNI bindings for dev.array21.skinfixer.storage.LibSkinFixer#getSkinProfile() - -use crate::config::StorageType; -use crate::jstring_to_string; -use crate::jni::Skin; - -use jni::objects::{JClass, JString, JObject}; -use mysql::prelude::Queryable; -use mysql::{params, Params}; -use std::path::PathBuf; -use jni::sys::jarray; -use jni::JNIEnv; -use std::fs; - -/// Java JNI function -/// -/// dev.array21.skinfixer.storage.LibSkinFixer#getSkinProfile(String uuid) -#[no_mangle] -pub extern "system" fn Java_dev_array21_skinfixer_storage_LibSkinFixer_getSkinProfile(env: JNIEnv<'_>, _class: JClass<'_>, uuid: JString<'_>) -> jarray { - let uuid = jstring_to_string!(env, uuid); - let config_guard = crate::config::CONFIG.lock().expect("Failed to lock CONFIG"); - let config_ref = match config_guard.try_borrow() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - let config = config_ref.as_ref().unwrap(); - - let string_class = env.find_class("java/lang/String").unwrap(); - - match config.get_type() { - StorageType::Mysql => { - let mut conn = match config.mysql_conn() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - let row = match conn.exec_first::("SELECT value,signature FROM skins WHERE uuid = :uuid", params! { - "uuid" => &uuid - }) { - Ok(rows) => rows, - Err(e) => panic!("Failed to query table 'skins': {:?}", e) - }; - - match row { - Some(row) => { - let value = row.get::("value").unwrap(); - let signature = row.get::("signature").unwrap(); - - let j_value = env.new_string(value).unwrap(); - let j_signature = env.new_string(signature).unwrap(); - - let array = env.new_object_array(2, string_class, JObject::null()).unwrap(); - env.set_object_array_element(array, 0, j_value).unwrap(); - env.set_object_array_element(array, 1, j_signature).unwrap(); - - array - }, - None => { - env.new_object_array(0, string_class, JObject::null()).unwrap() - } - } - }, - StorageType::Postgres => { - let mut conn = match config.postgres_conn() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - let rows = match conn.query("SELECT value,signature FROM skins WHERE uuid = $1", &[&uuid]) { - Ok(r) => r, - Err(e) => panic!("Failed to query table 'skins': {:?}", e) - }; - - if rows.is_empty() { - env.new_object_array(0, string_class, JObject::null()).unwrap() - } else { - let row = rows.get(0).unwrap(); - let value = row.get::<&str, String>("value"); - let signature = row.get::<&str, String>("signature"); - - let j_value = env.new_string(value).unwrap(); - let j_signature = env.new_string(signature).unwrap(); - - let array = env.new_object_array(2, string_class, JObject::null()).unwrap(); - env.set_object_array_element(array, 0, j_value).unwrap(); - env.set_object_array_element(array, 1, j_signature).unwrap(); - - array - } - }, - StorageType::Bin => { - let mut path = PathBuf::from(config.get_path().unwrap()); - path.push("skins.bin"); - - let contents = match fs::read(&path) { - Ok(c) => c, - Err(e) => panic!("Failed to open storage bin: {:?}", e) - }; - - if contents.is_empty() { - return env.new_object_array(0, string_class, JObject::null()).unwrap(); - } - - let skins: Vec = match bincode::deserialize(&contents) { - Ok(s) => s, - Err(e) => panic!("Failed to deserialize storage bin: {:?}", e) - }; - - let skins_match = skins.into_iter().flat_map(|x| { - if x.uuid.eq(&uuid) { - Ok(x) - } else { Err(()) } - }).collect::>(); - - if skins_match.is_empty() { - env.new_object_array(0, string_class, JObject::null()).unwrap() - } else { - let skin = skins_match.get(0).unwrap(); - let j_value = env.new_string(&skin.value).unwrap(); - let j_signature = env.new_string(&skin.signature).unwrap(); - - let array = env.new_object_array(2, string_class, JObject::null()).unwrap(); - env.set_object_array_element(array, 0, j_value).unwrap(); - env.set_object_array_element(array, 1, j_signature).unwrap(); - - array - } - }, - StorageType::Sqlite => { - use rusqlite::named_params; - - let conn = match config.sqlite_conn() { - Ok(c) => c, - Err(e) => panic!("Failed to create SQLite connection: {:?}", e) - }; - - let mut stmt = conn.prepare("SELECT value,signature FROM skins WHERE uuid = :uuid").unwrap(); - let mut rows = match stmt.query(named_params! { - ":uuid": uuid - }) { - Ok(r) => r, - Err(e) => panic!("Failed to query table 'skins': {:?}", e) - }; - - if let Ok(Some(row)) = rows.next() { - let value = row.get::<&str, String>("value").unwrap(); - let signature = row.get::<&str, String>("signature").unwrap(); - - let j_value = env.new_string(&value).unwrap(); - let j_signature = env.new_string(&signature).unwrap(); - - let array = env.new_object_array(2, string_class, JObject::null()).unwrap(); - env.set_object_array_element(array, 0, j_value).unwrap(); - env.set_object_array_element(array, 1, j_signature).unwrap(); - - return array; - } - env.new_object_array(0, string_class, JObject::null()).unwrap() - } - } -} \ No newline at end of file +//! JNI bindings for dev.array21.skinfixer.storage.LibSkinFixer#getSkinProfile() + +use crate::jstring_to_string; + +use crate::database::Profile; +use crate::jni::{DRIVER, TOKIO_RT}; +use jni::objects::{JClass, JObject, JString}; +use jni::sys::jarray; +use jni::JNIEnv; + +/// Java JNI function +/// +/// dev.array21.skinfixer.storage.LibSkinFixer#getSkinProfile(String uuid) +#[no_mangle] +pub extern "system" fn Java_dev_array21_skinfixer_storage_LibSkinFixer_getSkinProfile( + env: JNIEnv<'_>, + _class: JClass<'_>, + uuid: JString<'_>, +) -> jarray { + let uuid = jstring_to_string!(env, uuid); + + let string_class = env.find_class("java/lang/String").unwrap(); + + let tokio_rt = TOKIO_RT.get().expect("Getting global Tokio runtime"); + let _guard = tokio_rt.enter(); + + let driver = DRIVER.get().expect("Getting global storage driver"); + + let profile = tokio_rt + .block_on(Profile::get_by_uuid(driver, &uuid)) + .expect("Fetching skin"); + + match profile { + Some(profile) => { + let j_value = env + .new_string(profile.value) + .expect("Creating Java string for 'Value'"); + let j_signature = env + .new_string(profile.signature) + .expect("Creating Java string for 'Signature'"); + + let array = env + .new_object_array(2, string_class, JObject::null()) + .expect("Creating array for return values"); + + match env.set_object_array_element(array, 0, j_value) { + Ok(_) => {} + Err(_) => return empty_array(string_class, &env), + } + + match env.set_object_array_element(array, 1, j_signature) { + Ok(_) => {} + Err(_) => return empty_array(string_class, &env), + } + + array + } + None => empty_array(string_class, &env), + } +} + +fn empty_array(string_class: JClass<'_>, env: &JNIEnv<'_>) -> jarray { + env.new_object_array(0, string_class, JObject::null()) + .expect("Creating empty array") +} diff --git a/lib/src/jni/init.rs b/lib/src/jni/init.rs index 9f5e4ff..d884ebf 100755 --- a/lib/src/jni/init.rs +++ b/lib/src/jni/init.rs @@ -1,81 +1,70 @@ -//! JNI bindings for dev.array21.skinfixer.storage.LibSkinFixer#init() - -use crate::{jstring_to_string, optional_string}; -use crate::config::{StorageType, Config}; - -use jni::objects::{JClass, JString}; -use mysql::prelude::Queryable; -use std::path::PathBuf; -use std::str::FromStr; -use mysql::Params; -use jni::JNIEnv; - -/// Java JNI function -/// -/// dev.array21.skinfixer.storage.LibSkinFixer#init(String storageType, String host, String database, String username, String password, String storagePath) -#[no_mangle] -pub extern "system" fn Java_dev_array21_skinfixer_storage_LibSkinFixer_init(env: JNIEnv<'_>, _class: JClass<'_>, storage_type: JString<'_>, host: JString<'_>, database: JString<'_>, username: JString<'_>, password: JString<'_>, storage_path: JString<'_>) { - let storage_type_str: String = jstring_to_string!(env, storage_type); - let storage_type = match StorageType::from_str(&storage_type_str) { - Ok(st) => st, - Err(_) => panic!("Failed to convert String to StorageType, '{}' is not valid", &storage_type_str) - }; - - let host = jstring_to_string!(env, host); - let database = jstring_to_string!(env, database); - let username = jstring_to_string!(env, username); - let password = jstring_to_string!(env, password); - let storage_path = jstring_to_string!(env, storage_path); - - let config = Config::new(storage_type, optional_string!(host), optional_string!(database), optional_string!(username), optional_string!(password), optional_string!(storage_path)); - - match config.get_type() { - StorageType::Mysql => { - let mut conn = match config.mysql_conn() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - match conn.exec::("CREATE TABLE IF NOT EXISTS skins (uuid VARCHAR(36) PRIMARY KEY, value TEXT, signature TEXT)", Params::Empty) { - Ok(_) => {}, - Err(e) => panic!("Failed to create table 'skins': {:?}", e) - } - }, - StorageType::Postgres => { - let mut conn = match config.postgres_conn() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - match conn.execute("CREATE TABLE IF NOT EXISTS skins (uuid VARCHAR(36) PRIMARY KEY, value TEXT, signature TEXT)", &[]) { - Ok(_) => {}, - Err(e) => panic!("Failed to create table 'skins': {:?}", e) - } - }, - StorageType::Bin => { - let mut path = PathBuf::from(config.get_path().unwrap()); - path.push("skins.bin"); - - if !path.exists() { - match std::fs::File::create(&path) { - Ok(_) => {}, - Err(e) => panic!("Failed to create skins.bin file: {:?}", e) - } - } - }, - StorageType::Sqlite => { - let conn = match config.sqlite_conn() { - Ok(c) => c, - Err(e) => panic!("Failed to create SQLite connection: {:?}", e) - }; - - match conn.execute("CREATE TABLE IF NOT EXISTS skins (uuid TEXT PRIMARY KEY, value TEXT, signature TEXT)", rusqlite::params!{}) { - Ok(_) => {}, - Err(e) => panic!("Failed to create table 'skins': {:?}", e) - } - } - } - - let lock = crate::config::CONFIG.lock().expect("Failed to lock CONFIG Mutex"); - lock.replace(Some(config)); -} \ No newline at end of file +//! JNI bindings for dev.array21.skinfixer.storage.LibSkinFixer#init() + +use crate::jstring_to_string; + +use crate::database::{DatabaseOptions, Driver, DriverType}; +use crate::jni::{DRIVER, TOKIO_RT}; +use jni::objects::{JClass, JString}; +use jni::sys::jchar; +use jni::JNIEnv; +use std::path::PathBuf; + +/// Java JNI function +/// +/// dev.array21.skinfixer.storage.LibSkinFixer#init(String storageType, String host, String database, String username, String password, String storagePath), char port +#[no_mangle] +pub extern "system" fn Java_dev_array21_skinfixer_storage_LibSkinFixer_init( + env: JNIEnv<'_>, + _class: JClass<'_>, + storage_type: JString<'_>, + host: JString<'_>, + database: JString<'_>, + username: JString<'_>, + password: JString<'_>, + storage_path: JString<'_>, + port: jchar, +) { + let storage_type = jstring_to_string!(env, storage_type); + + let host = jstring_to_string!(env, host); + let name = jstring_to_string!(env, database); + let user = jstring_to_string!(env, username); + let passw = jstring_to_string!(env, password); + let storage_path = jstring_to_string!(env, storage_path); + + let driver_type = match storage_type.as_str() { + "mysql" => DriverType::Mysql(DatabaseOptions { + host: &host, + user: &user, + passw: &passw, + name: &name, + port, + }), + "postgres" => DriverType::Postgres(DatabaseOptions { + host: &host, + user: &user, + passw: &passw, + name: &name, + port, + }), + "sqlite" => DriverType::Sqlite(PathBuf::from(storage_path)), + "bin" => DriverType::Bin(PathBuf::from(storage_path)), + _ => panic!("Unknown storage type '{storage_type}'"), + }; + + let tokio_rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + + let _guard = tokio_rt.enter(); + + let driver = tokio_rt + .block_on(Driver::new(driver_type)) + .expect("Initializing storage driver"); + + DRIVER.set(driver).expect("Setting global driver"); + TOKIO_RT + .set(tokio_rt) + .expect("Setting global Tokio runtime"); +} diff --git a/lib/src/jni/mod.rs b/lib/src/jni/mod.rs index d94fad9..d11908e 100755 --- a/lib/src/jni/mod.rs +++ b/lib/src/jni/mod.rs @@ -1,42 +1,24 @@ -//! This module contains all JNI related code - -mod init; -mod get_skin_profile; -mod del_skin_profile; -mod set_skin_profile; - -use serde::{Serialize, Deserialize}; - -/// Convert a [jni::JString] to a String, panic! if the conversion failed -#[macro_export] -macro_rules! jstring_to_string { - ($env:expr, $expression:expr) => { - String::from(match $env.get_string($expression) { - Ok(jstr) => jstr, - Err(e) => panic!("Failed to convert JString to String: {:?}", e) - }) - } -} - -/// 'Unwrap' a String to None or Some(string), where the return value is None when the String is empty -#[macro_export] -macro_rules! optional_string { - ($expression:expr) => { - if $expression.is_empty() { - None - } else { - Some($expression) - } - } -} - -/// Struct describing a Skin -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Skin { - /// The UUID of the owner - pub uuid: String, - /// The skin's value - pub value: String, - /// the skin's signature - pub signature: String -} \ No newline at end of file +//! This module contains all JNI related code + +mod del_skin_profile; +mod get_skin_profile; +mod init; +mod set_skin_profile; + +use crate::database::Driver; +use std::sync::OnceLock; +use tokio::runtime::Runtime; + +static TOKIO_RT: OnceLock = OnceLock::new(); +static DRIVER: OnceLock = OnceLock::new(); + +/// Convert a [jni::JString] to a String, panic! if the conversion failed +#[macro_export] +macro_rules! jstring_to_string { + ($env:expr, $expression:expr) => { + String::from(match $env.get_string($expression) { + Ok(jstr) => jstr, + Err(e) => panic!("Failed to convert JString to String: {:?}", e), + }) + }; +} diff --git a/lib/src/jni/set_skin_profile.rs b/lib/src/jni/set_skin_profile.rs index c336e54..2456840 100755 --- a/lib/src/jni/set_skin_profile.rs +++ b/lib/src/jni/set_skin_profile.rs @@ -1,129 +1,31 @@ -//! JNI bindings for dev.array21.skinfixer.storage.LibSkinFixer#setSkinProfile() - -use crate::config::StorageType; -use crate::jstring_to_string; -use crate::jni::Skin; - -use jni::objects::{JClass, JString}; -use mysql::prelude::Queryable; -use mysql::{Params, params}; -use std::path::PathBuf; -use std::io::Write; -use jni::JNIEnv; -use std::fs; - -/// Java JNI function -/// -/// dev.array21.skinfixer.storage.LibSkinFixer#setSkinProfile(String uuid, String value, String signature) -#[no_mangle] -pub extern "system" fn Java_dev_array21_skinfixer_storage_LibSkinFixer_setSkinProfile(env: JNIEnv<'_>, _class: JClass<'_>, uuid: JString<'_>, value: JString<'_>, signature: JString<'_>) { - let uuid = jstring_to_string!(env, uuid); - let value = jstring_to_string!(env, value); - let signature = jstring_to_string!(env, signature); - - let config_guard = crate::config::CONFIG.lock().expect("Failed to lock CONFIG"); - let config_ref = match config_guard.try_borrow() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - let config = config_ref.as_ref().unwrap(); - - match config.get_type() { - StorageType::Mysql => { - let mut conn = match config.mysql_conn() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - match conn.exec::("INSERT INTO skins (uuid, value, signature) VALUES (:uuid, :value, :signature)", params! { - "uuid" => &uuid, - "value" => &value, - "signature" => &signature - }) { - Ok(_) => {}, - Err(e) => panic!("Failed to insert skin into mysql database: {:?}", e) - } - }, - StorageType::Postgres => { - let mut conn = match config.postgres_conn() { - Ok(c) => c, - Err(e) => panic!("{:?}", e) - }; - - match conn.execute("INSERT INTO skins (uuid, value, signature) VALUES ($1, $2, $3)", &[&uuid, &value, &signature]) { - Ok(_) => {} - Err(e) => panic!("Failed to insert skin into postgres database: {:?}", e) - } - }, - StorageType::Bin => { - let mut path = PathBuf::from(config.get_path().unwrap()); - path.push("skins.bin"); - - let contents = match fs::read(&path) { - Ok(c) => c, - Err(e) => panic!("Failed to open storage bin: {:?}", e) - }; - - let mut skins: Vec = if contents.is_empty() { - Vec::new() - } else { - match bincode::deserialize(&contents) { - Ok(s) => s, - Err(e) => panic!("Failed to deserialize storage bin: {:?}", e) - } - }; - - let new_skin = Skin { - uuid: (*uuid).to_string(), - value: (*value).to_string(), - signature: (*signature).to_string() - }; - - let mut skin_contained = false; - skins.iter_mut().for_each(|x| { - if x.uuid.eq(&uuid) { - *x = new_skin.clone(); - skin_contained = true; - } - }); - - if !skin_contained { - skins.push(new_skin); - } - - let bytes = match bincode::serialize(&skins) { - Ok(b) => b, - Err(e) => panic!("Failed to deserialize Skins vector into bytes: {:?}", e) - }; - - let mut file = match fs::File::create(&path) { - Ok(f) => f, - Err(e) => panic!("Failed to open storage bin: {:?}", e) - }; - - match file.write_all(&bytes) { - Ok(_) => {}, - Err(e) => panic!("Failed to write to skins.bin file: {:?}", e) - } - }, - StorageType::Sqlite => { - use rusqlite::named_params; - - let conn = match config.sqlite_conn() { - Ok(c) => c, - Err(e) => panic!("Failed to create SQLite connection: {:?}", e) - }; - - let mut stmt = conn.prepare("INSERT INTO skins (uuid, value, signature) VALUES (:uuid, :value, :signature)").unwrap(); - match stmt.execute(named_params! { - ":uuid": uuid, - ":value": value, - ":signature": signature - }) { - Ok(_) => {}, - Err(e) => panic!("Failed to insert skin into database: {:?}", e) - } - } - } -} \ No newline at end of file +//! JNI bindings for dev.array21.skinfixer.storage.LibSkinFixer#setSkinProfile() + +use crate::jstring_to_string; + +use crate::database::Profile; +use crate::jni::{DRIVER, TOKIO_RT}; +use jni::objects::{JClass, JString}; +use jni::JNIEnv; + +/// Java JNI function +/// +/// dev.array21.skinfixer.storage.LibSkinFixer#setSkinProfile(String uuid, String value, String signature) +#[no_mangle] +pub extern "system" fn Java_dev_array21_skinfixer_storage_LibSkinFixer_setSkinProfile( + env: JNIEnv<'_>, + _class: JClass<'_>, + uuid: JString<'_>, + value: JString<'_>, + signature: JString<'_>, +) { + let uuid = jstring_to_string!(env, uuid); + let value = jstring_to_string!(env, value); + let signature = jstring_to_string!(env, signature); + + let tokio_rt = TOKIO_RT.get().expect("Getting global Tokio runtime"); + let _guard = tokio_rt.enter(); + + let driver = DRIVER.get().expect("Getting global storage driver"); + + let _ = tokio_rt.block_on(Profile::set_skin_profile(driver, &uuid, &value, &signature)); +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index fb282fa..69cda5c 100755 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,9 +1,7 @@ //! LibSkinFixer -//! This library is a companion library to the Java project [SkinFixer](https://github.com/TheDutchMC/SkinFixer). SkinFixer is a Minecraft Spigot plugin focused on making skins better +//! This library is a companion library to the Java project [SkinFixer](https://github.com/TobiasDeBruijn/SkinFixer). SkinFixer is a Minecraft Spigot plugin focused on making skins better #![deny(deprecated)] -//#![deny(clippy::panic)] - #![deny(rust_2018_idioms)] #![deny(clippy::decimal_literal_representation)] #![deny(clippy::if_not_else)] @@ -12,5 +10,5 @@ #![deny(clippy::missing_errors_doc)] #![deny(clippy::needless_continue)] -pub mod jni; -pub mod config; \ No newline at end of file +mod database; +mod jni; diff --git a/plugininfo.json b/plugininfo.json index d8e6d6a..676ad3a 100644 --- a/plugininfo.json +++ b/plugininfo.json @@ -2,9 +2,5 @@ "servers": [ "https://skinfixer.k8s.array21.dev" ], - "messages": [ - { - "text": "Poll for server admins: If you use the databaseType 'MYSQL', 'POSTGRES' or 'SQLITE', please let me know here: https://github.com/TobiasDeBruijn/SkinFixer/issues/53" - } - ] + "messages": [] } \ No newline at end of file diff --git a/src/main/java/dev/array21/skinfixer/SkinFixer.java b/src/main/java/dev/array21/skinfixer/SkinFixer.java index 856493c..5aeba8d 100644 --- a/src/main/java/dev/array21/skinfixer/SkinFixer.java +++ b/src/main/java/dev/array21/skinfixer/SkinFixer.java @@ -37,7 +37,7 @@ public void onEnable() { PLUGIN_VERSION = this.getDescription().getVersion(); LOGGER = this.getLogger(); - SkinFixer.logInfo("Welcome to SkinFixer version " + PLUGIN_VERSION + " by TheDutchMC!"); + SkinFixer.logInfo("Welcome to SkinFixer version " + PLUGIN_VERSION + " by TobiasDeBruijn!"); //Read the configuration this.configHandler = new ConfigHandler(this); diff --git a/src/main/java/dev/array21/skinfixer/config/ConfigHandler.java b/src/main/java/dev/array21/skinfixer/config/ConfigHandler.java index 413e1c9..928c113 100644 --- a/src/main/java/dev/array21/skinfixer/config/ConfigHandler.java +++ b/src/main/java/dev/array21/skinfixer/config/ConfigHandler.java @@ -1,98 +1,98 @@ -package dev.array21.skinfixer.config; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileWriter; -import java.io.IOException; - -import org.bukkit.Bukkit; -import org.yaml.snakeyaml.Yaml; - -import com.google.gson.Gson; - -import dev.array21.classvalidator.ClassValidator; -import dev.array21.classvalidator.Pair; -import dev.array21.skinfixer.SkinFixer; -import dev.array21.skinfixer.util.Utils; - -public class ConfigHandler { - - private final SkinFixer plugin; - private ConfigManifest manifest; - - private final static Yaml YAML = new Yaml(); - private final static Gson GSON = new Gson(); - - public ConfigHandler(SkinFixer plugin) { - this.plugin = plugin; - } - - public ConfigManifest read() { - File configFile = new File(this.plugin.getDataFolder(), "config.yml"); - if(!configFile.exists()) { - configFile.getParentFile().mkdirs(); - this.plugin.saveResource("config.yml", false); - } - - Object yaml; - try { - yaml = YAML.load(new FileInputStream(configFile)); - } catch(FileNotFoundException e) { - return null; - } - - String json = GSON.toJson(yaml, java.util.LinkedHashMap.class); - ConfigManifest manifest = GSON.fromJson(json, ConfigManifest.class); - - Pair validation = ClassValidator.validateType(manifest); - if(validation.getA() == null) { - SkinFixer.logWarn(String.format("Failed to validate configuration file due to a fatal error: %s", validation.getB())); - Bukkit.getPluginManager().disablePlugin(this.plugin); - return null; - } - - if(!validation.getA()) { - SkinFixer.logWarn(String.format("Configuration is invalid: %s", validation.getB())); - Bukkit.getPluginManager().disablePlugin(this.plugin); - return null; - } - - if(manifest.applySkinOnJoin == null) { - manifest.applySkinOnJoin = true; - } - - if(manifest.remotePluginInfoUrl == null) { - manifest.remotePluginInfoUrl = "https://raw.githubusercontent.com/TobiasDeBruijn/SkinFixer/master/plugininfo.json"; - } - - if(manifest.useDiscord) { - if(manifest.discordSettings == null) { - SkinFixer.logWarn("Configuration is invalid: useDiscord is set to true, but discordSettings is missing."); - Bukkit.getPluginManager().disablePlugin(this.plugin); - return null; - } - - if(manifest.discordSettings.token == null || manifest.discordSettings.token.isEmpty()) { - SkinFixer.logWarn("Configuration is invalid: useDiscord is set to true, but 'discordSettings.token' was left blank."); - Bukkit.getPluginManager().disablePlugin(this.plugin); - return null; - } - - if(manifest.discordSettings.channelId == null || manifest.discordSettings.channelId == 0) { - SkinFixer.logWarn("Configuration is invalid: useDiscord is set to true, but 'discordSettings.channelId' was left blank."); - Bukkit.getPluginManager().disablePlugin(this.plugin); - return null; - } - } - - this.manifest = manifest; - return manifest; - } - - public ConfigManifest getConfigManifest() { - return this.manifest; - } - -} +package dev.array21.skinfixer.config; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; + +import org.bukkit.Bukkit; +import org.yaml.snakeyaml.Yaml; + +import com.google.gson.Gson; + +import dev.array21.classvalidator.ClassValidator; +import dev.array21.classvalidator.Pair; +import dev.array21.skinfixer.SkinFixer; +import dev.array21.skinfixer.util.Utils; + +public class ConfigHandler { + + private final SkinFixer plugin; + private ConfigManifest manifest; + + private final static Yaml YAML = new Yaml(); + private final static Gson GSON = new Gson(); + + public ConfigHandler(SkinFixer plugin) { + this.plugin = plugin; + } + + public ConfigManifest read() { + File configFile = new File(this.plugin.getDataFolder(), "config.yml"); + if(!configFile.exists()) { + configFile.getParentFile().mkdirs(); + this.plugin.saveResource("config.yml", false); + } + + Object yaml; + try { + yaml = YAML.load(new FileInputStream(configFile)); + } catch(FileNotFoundException e) { + return null; + } + + String json = GSON.toJson(yaml, java.util.LinkedHashMap.class); + ConfigManifest manifest = GSON.fromJson(json, ConfigManifest.class); + + Pair validation = ClassValidator.validateType(manifest); + if(validation.getA() == null) { + SkinFixer.logWarn(String.format("Failed to validate configuration file due to a fatal error: %s", validation.getB())); + Bukkit.getPluginManager().disablePlugin(this.plugin); + return null; + } + + if(!validation.getA()) { + SkinFixer.logWarn(String.format("Configuration is invalid: %s", validation.getB())); + Bukkit.getPluginManager().disablePlugin(this.plugin); + return null; + } + + if(manifest.applySkinOnJoin == null) { + manifest.applySkinOnJoin = true; + } + + if(manifest.remotePluginInfoUrl == null) { + manifest.remotePluginInfoUrl = "https://raw.githubusercontent.com/TobiasDeBruijn/SkinFixer/master/plugininfo.json"; + } + + if(manifest.useDiscord) { + if(manifest.discordSettings == null) { + SkinFixer.logWarn("Configuration is invalid: useDiscord is set to true, but discordSettings is missing."); + Bukkit.getPluginManager().disablePlugin(this.plugin); + return null; + } + + if(manifest.discordSettings.token == null || manifest.discordSettings.token.isEmpty()) { + SkinFixer.logWarn("Configuration is invalid: useDiscord is set to true, but 'discordSettings.token' was left blank."); + Bukkit.getPluginManager().disablePlugin(this.plugin); + return null; + } + + if(manifest.discordSettings.channelId == null || manifest.discordSettings.channelId == 0) { + SkinFixer.logWarn("Configuration is invalid: useDiscord is set to true, but 'discordSettings.channelId' was left blank."); + Bukkit.getPluginManager().disablePlugin(this.plugin); + return null; + } + } + + this.manifest = manifest; + return manifest; + } + + public ConfigManifest getConfigManifest() { + return this.manifest; + } + +} diff --git a/src/main/java/dev/array21/skinfixer/config/SqlSettings.java b/src/main/java/dev/array21/skinfixer/config/SqlSettings.java index 09c4ce0..0c2e300 100644 --- a/src/main/java/dev/array21/skinfixer/config/SqlSettings.java +++ b/src/main/java/dev/array21/skinfixer/config/SqlSettings.java @@ -5,4 +5,6 @@ public class SqlSettings { public String database; public String username; public String password; + + public Character port; } diff --git a/src/main/java/dev/array21/skinfixer/storage/LibSkinFixer.java b/src/main/java/dev/array21/skinfixer/storage/LibSkinFixer.java index 7f7f083..902ed4f 100644 --- a/src/main/java/dev/array21/skinfixer/storage/LibSkinFixer.java +++ b/src/main/java/dev/array21/skinfixer/storage/LibSkinFixer.java @@ -2,7 +2,7 @@ public class LibSkinFixer { - protected static native void init(String type, String host, String database, String username, String password, String storage_path); + protected static native void init(String type, String host, String database, String username, String password, String storage_path, char port); protected static native String[] getSkinProfile(String uuid); protected static native void setSkinProfile(String uuid, String value, String signature); protected static native void delSkinProfile(String uuid); diff --git a/src/main/java/dev/array21/skinfixer/storage/LibWrapper.java b/src/main/java/dev/array21/skinfixer/storage/LibWrapper.java index 89b5efa..467d13a 100644 --- a/src/main/java/dev/array21/skinfixer/storage/LibWrapper.java +++ b/src/main/java/dev/array21/skinfixer/storage/LibWrapper.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.net.URL; import java.nio.file.Files; +import java.util.Locale; import java.util.UUID; import java.util.regex.Pattern; @@ -70,7 +71,6 @@ public class LibWrapper { SkinFixer.logWarn(Utils.getStackTrace(e)); } - SkinFixer.logInfo("libskinfixer loaded."); LIB_LOADED = true; } } @@ -120,22 +120,38 @@ public void init() { ConfigManifest configManifest = this.plugin.getConfigManifest(); String host, database, username, password; + char port; + SqlSettings sqlSettings = configManifest.sqlSettings; + if(sqlSettings != null) { host = (sqlSettings.host != null) ? sqlSettings.host : ""; database = (sqlSettings.database != null) ? sqlSettings.database : ""; username = (sqlSettings.username != null) ? sqlSettings.username : ""; - password = (sqlSettings.password != null) ? sqlSettings.password : ""; - } else { + password = (sqlSettings.password != null) ? sqlSettings.password : ""; + + port = sqlSettings.port != null ? sqlSettings.port : (char) switch (configManifest.databaseType) { + case MYSQL -> 3306; + case POSTGRES -> 5432; + default -> 0; + }; + } else { host = database = username = password = ""; + port = 0; } File pluginFolder = this.plugin.getDataFolder(); if(!pluginFolder.exists()) { pluginFolder.mkdirs(); } - - LibSkinFixer.init(configManifest.databaseType.toString(), host, database, username, password, pluginFolder.getAbsolutePath()); + + String storageFile = switch(configManifest.databaseType) { + case SQLITE -> new File(pluginFolder.getAbsolutePath(), "skins.sqlite").getAbsolutePath(); + case BIN -> new File(pluginFolder.getAbsolutePath(), "skins.bin").getAbsolutePath(); + default -> null; + }; + + LibSkinFixer.init(configManifest.databaseType.toString().toLowerCase(Locale.ROOT), host, database, username, password, storageFile, port); } public SkinData getSkinProfile(UUID owner) {