2020-10-29 08:01:36 +00:00
package net.limework.rediskript.managers ;
2020-05-10 06:52:06 +00:00
2020-11-25 15:01:11 +00:00
import ch.njol.skript.registrations.Classes ;
import ch.njol.skript.variables.Variables ;
2020-10-29 08:01:36 +00:00
import net.limework.rediskript.RediSkript ;
import net.limework.rediskript.data.Encryption ;
2020-11-26 15:05:07 +00:00
import net.limework.rediskript.events.RedisMessageEvent ;
2020-05-10 06:52:06 +00:00
import org.bukkit.Bukkit ;
2020-06-28 14:40:53 +00:00
import org.bukkit.configuration.Configuration ;
2020-12-23 17:04:23 +00:00
import org.bukkit.scheduler.BukkitTask ;
2020-06-08 13:53:23 +00:00
import org.cryptomator.siv.UnauthenticCiphertextException ;
2020-11-25 11:53:52 +00:00
import org.json.JSONArray ;
2020-05-10 06:52:06 +00:00
import org.json.JSONObject ;
2020-12-23 17:04:23 +00:00
import redis.clients.jedis.* ;
2020-11-25 15:01:11 +00:00
import redis.clients.jedis.exceptions.JedisConnectionException ;
2020-05-10 06:52:06 +00:00
2020-06-08 13:53:23 +00:00
import javax.crypto.IllegalBlockSizeException ;
2020-10-22 09:32:08 +00:00
import java.nio.charset.StandardCharsets ;
2020-11-25 15:01:11 +00:00
import java.util.Base64 ;
2020-05-10 06:52:06 +00:00
import java.util.List ;
2020-06-14 13:43:46 +00:00
import java.util.concurrent.atomic.AtomicBoolean ;
2020-05-10 06:52:06 +00:00
2020-12-23 17:04:23 +00:00
public class RedisController extends BinaryJedisPubSub implements Runnable {
2020-05-10 19:10:45 +00:00
2020-12-23 17:04:23 +00:00
//Jedis Pool to be used by every another class.
private final JedisPool jedisPool ;
2020-06-28 14:40:53 +00:00
2020-12-23 17:04:23 +00:00
//this seems useless unless tls is OFF!
private final Encryption encryption ;
2020-06-28 14:40:53 +00:00
2020-12-23 17:04:23 +00:00
private byte [ ] [ ] channelsInByte ;
2020-06-14 13:43:46 +00:00
2020-12-23 17:04:23 +00:00
private final AtomicBoolean isConnectionBroken ;
2020-12-25 19:27:06 +00:00
private final AtomicBoolean isConnecting ;
2020-12-23 17:04:23 +00:00
private final RediSkript plugin ;
2020-12-25 17:43:20 +00:00
private final BukkitTask ConnectionTask ;
2020-05-10 19:10:45 +00:00
2020-12-23 17:04:23 +00:00
public RedisController ( RediSkript plugin ) {
2020-05-10 06:52:06 +00:00
this . plugin = plugin ;
2020-12-23 17:04:23 +00:00
Configuration config = plugin . getConfig ( ) ;
2020-06-28 14:40:53 +00:00
JedisPoolConfig JConfig = new JedisPoolConfig ( ) ;
2020-10-24 10:29:49 +00:00
int maxConnections = config . getInt ( " Redis.MaxConnections " ) ;
2021-01-26 08:51:31 +00:00
//do not allow less than 2 max connections as that causes issues
if ( maxConnections < 2 ) { maxConnections = 2 ; }
2020-10-24 10:29:49 +00:00
JConfig . setMaxTotal ( maxConnections ) ;
JConfig . setMaxIdle ( maxConnections ) ;
2020-06-28 14:40:53 +00:00
JConfig . setMinIdle ( 1 ) ;
JConfig . setBlockWhenExhausted ( true ) ;
this . jedisPool = new JedisPool ( JConfig ,
config . getString ( " Redis.Host " ) ,
config . getInt ( " Redis.Port " ) ,
config . getInt ( " Redis.TimeOut " ) ,
config . getString ( " Redis.Password " ) ,
2020-10-24 09:35:35 +00:00
config . getBoolean ( " Redis.useTLS " ) ) ;
2020-06-28 14:40:53 +00:00
encryption = new Encryption ( config ) ;
2020-12-23 17:04:23 +00:00
setupChannels ( config ) ;
isConnectionBroken = new AtomicBoolean ( true ) ;
2020-12-25 19:27:06 +00:00
isConnecting = new AtomicBoolean ( false ) ;
2020-12-23 17:04:23 +00:00
//Start the main task on async thread
2020-12-25 19:27:06 +00:00
ConnectionTask = plugin . getServer ( ) . getScheduler ( ) . runTaskTimerAsynchronously ( plugin , this , 0 , 20 * 5 ) ;
2020-05-10 19:10:45 +00:00
}
2020-05-10 17:44:10 +00:00
2020-05-10 19:10:45 +00:00
@Override
2020-06-26 13:02:39 +00:00
public void run ( ) {
2020-12-25 19:27:06 +00:00
if ( ! isConnectionBroken . get ( ) | | isConnecting . get ( ) ) {
2020-12-23 17:04:23 +00:00
return ;
}
2021-01-26 08:51:31 +00:00
plugin . sendLogs ( " Connecting to Redis server... " ) ;
2020-12-25 19:27:06 +00:00
isConnecting . set ( true ) ;
2020-12-23 17:04:23 +00:00
try ( Jedis jedis = jedisPool . getResource ( ) ) {
isConnectionBroken . set ( false ) ;
2021-01-26 08:51:31 +00:00
plugin . sendLogs ( " &aConnection to Redis server has established! Success! " ) ;
2020-12-23 17:04:23 +00:00
jedis . subscribe ( this , channelsInByte ) ;
} catch ( Exception e ) {
2020-12-25 19:27:06 +00:00
isConnecting . set ( false ) ;
2020-12-23 17:04:23 +00:00
isConnectionBroken . set ( true ) ;
2021-01-26 08:51:31 +00:00
plugin . sendErrorLogs ( " Connection to Redis server has failed! Please check your details in the configuration. " ) ;
e . printStackTrace ( ) ;
2020-05-10 19:10:45 +00:00
}
2020-05-10 06:52:06 +00:00
}
2020-12-23 17:04:23 +00:00
public void shutdown ( ) {
ConnectionTask . cancel ( ) ;
2021-01-26 08:51:31 +00:00
if ( this . isSubscribed ( ) ) {
try {
this . unsubscribe ( ) ;
} catch ( Exception e ) {
plugin . sendErrorLogs ( " Something went wrong during unsubscribing... " ) ;
2020-12-26 10:03:27 +00:00
e . printStackTrace ( ) ;
}
2020-12-25 19:27:06 +00:00
}
2020-12-23 17:04:23 +00:00
jedisPool . close ( ) ;
}
2020-05-10 06:52:06 +00:00
@Override
2021-01-26 08:51:31 +00:00
public void onMessage ( byte [ ] channel , byte [ ] message ) {
String channelString = new String ( channel , StandardCharsets . UTF_8 ) ;
String receivedMessage = null ;
try {
//if encryption is enabled, decrypt the message, else just convert binary to string
if ( this . encryption . isEncryptionEnabled ( ) ) {
try {
receivedMessage = encryption . decrypt ( message ) ;
} catch ( UnauthenticCiphertextException | IllegalBlockSizeException e ) {
e . printStackTrace ( ) ;
2020-11-25 15:01:11 +00:00
}
2020-11-26 15:05:07 +00:00
2021-01-26 08:51:31 +00:00
} else {
//encryption is disabled, so let's just get the string
receivedMessage = new String ( message , StandardCharsets . UTF_8 ) ;
}
if ( receivedMessage ! = null ) {
JSONObject j = new JSONObject ( receivedMessage ) ;
if ( j . get ( " Type " ) . equals ( " Skript " ) ) {
JSONArray messages = j . getJSONArray ( " Messages " ) ;
RedisMessageEvent event ;
for ( int i = 0 ; i < messages . length ( ) ; i + + ) {
event = new RedisMessageEvent ( channelString , messages . get ( i ) . toString ( ) , j . getLong ( " Date " ) ) ;
//if plugin is disabling, don't call events anymore
if ( plugin . isEnabled ( ) ) {
RedisMessageEvent finalEvent = event ;
Bukkit . getScheduler ( ) . runTask ( plugin , ( ) - > plugin . getServer ( ) . getPluginManager ( ) . callEvent ( finalEvent ) ) ;
2020-11-26 15:05:07 +00:00
}
2021-01-26 08:51:31 +00:00
}
} else if ( j . get ( " Type " ) . equals ( " SkriptVariables " ) ) {
2020-11-26 15:05:07 +00:00
2021-01-26 08:51:31 +00:00
//Transfer variables between servers
JSONArray variableNames = j . getJSONArray ( " Names " ) ;
Object inputValue ;
String changeValue = null ;
JSONArray variableValues = null ;
if ( ! j . isNull ( " Values " ) ) {
variableValues = j . getJSONArray ( " Values " ) ;
}
for ( int i = 0 ; i < variableNames . length ( ) ; i + + ) {
if ( j . isNull ( " Values " ) ) {
//only check for SET here, because null has to be ignored in all other cases
if ( j . getString ( " Operation " ) . equals ( " SET " ) ) {
Variables . setVariable ( variableNames . get ( i ) . toString ( ) , null , null , false ) ;
}
} else {
if ( ! variableValues . isNull ( i ) ) {
changeValue = variableValues . get ( i ) . toString ( ) ;
}
String [ ] inputs = changeValue . split ( " \\ ^ " , 2 ) ;
inputValue = Classes . deserialize ( inputs [ 0 ] , Base64 . getDecoder ( ) . decode ( inputs [ 1 ] ) ) ;
switch ( j . getString ( " Operation " ) ) {
case " ADD " :
//I will add this once someone tells me how to remove from Skript variable
//because using SET operation has issues with inconvertible types (Double and Long)
//variable = (Variable) Variables.getVariable(variableNames.get(i).toString(), null, false);
// variable.change(null, (Object[]) inputValue, Changer.ChangeMode.REMOVE);
case " REMOVE " :
//I will add this once someone tells me how to remove from Skript variable
//because using SET operation has issues with inconvertible types (Double and Long)
//variable = (Variable) Variables.getVariable(variableNames.get(i).toString(), null, false);
// variable.change(null, (Object[]) inputValue, Changer.ChangeMode.REMOVE);
break ;
case " SET " :
Variables . setVariable ( variableNames . get ( i ) . toString ( ) , inputValue , null , false ) ;
}
2020-11-26 15:05:07 +00:00
}
2020-11-25 15:01:11 +00:00
}
}
}
2021-01-26 08:51:31 +00:00
} catch ( Exception e ) {
plugin . sendErrorLogs ( " &cI got a message that was empty from channel " + channelString + " please check your code that you used to send the message. Message content: " ) ;
2020-12-26 10:03:27 +00:00
plugin . sendErrorLogs ( receivedMessage ) ;
2021-01-26 08:51:31 +00:00
e . printStackTrace ( ) ;
2020-12-26 10:03:27 +00:00
}
2020-05-10 06:52:06 +00:00
}
2020-11-25 15:01:11 +00:00
public void sendMessage ( String [ ] message , String channel ) {
JSONObject json = new JSONObject ( ) ;
json . put ( " Messages " , new JSONArray ( message ) ) ;
json . put ( " Type " , " Skript " ) ;
json . put ( " Date " , System . currentTimeMillis ( ) ) ; //for unique string every time & PING calculations
finishSendMessage ( json , channel ) ;
}
2020-12-23 17:04:23 +00:00
2020-11-26 15:05:07 +00:00
public void sendVariables ( String [ ] variableNames , String [ ] variableValues , String channel , String operation ) {
2020-11-25 15:01:11 +00:00
JSONObject json = new JSONObject ( ) ;
json . put ( " Names " , new JSONArray ( variableNames ) ) ;
2020-11-26 15:05:07 +00:00
if ( variableValues ! = null ) {
json . put ( " Values " , new JSONArray ( variableValues ) ) ;
}
2020-11-25 15:01:11 +00:00
json . put ( " Type " , " SkriptVariables " ) ;
json . put ( " Date " , System . currentTimeMillis ( ) ) ; //for unique string every time & PING calculations
2020-11-26 15:05:07 +00:00
json . put ( " Operation " , operation ) ;
2020-11-25 15:01:11 +00:00
finishSendMessage ( json , channel ) ;
}
public void finishSendMessage ( JSONObject json , String channel ) {
try {
byte [ ] message ;
2020-12-23 17:04:23 +00:00
if ( encryption . isEncryptionEnabled ( ) ) {
message = encryption . encrypt ( json . toString ( ) ) ;
2020-11-25 15:01:11 +00:00
} else {
message = json . toString ( ) . getBytes ( StandardCharsets . UTF_8 ) ;
}
//sending a redis message blocks main thread if there's no more connections available
//so to avoid issues, it's best to do it always on separate thread
if ( plugin . isEnabled ( ) ) {
2020-12-23 17:04:23 +00:00
plugin . getServer ( ) . getScheduler ( ) . runTaskAsynchronously ( plugin , ( ) - > {
try ( BinaryJedis j = jedisPool . getResource ( ) ) {
j . publish ( channel . getBytes ( StandardCharsets . UTF_8 ) , message ) ;
} catch ( Exception e ) {
2021-01-26 08:51:31 +00:00
plugin . sendErrorLogs ( " Error sending redis message! " ) ;
e . printStackTrace ( ) ;
2020-12-26 11:07:12 +00:00
}
2020-11-25 15:01:11 +00:00
} ) ;
} else {
2020-12-03 09:55:45 +00:00
//execute sending of redis message on the main thread if plugin is disabling
//so it can still process the sending
2020-12-23 17:04:23 +00:00
try ( BinaryJedis j = jedisPool . getResource ( ) ) {
j . publish ( channel . getBytes ( StandardCharsets . UTF_8 ) , message ) ;
} catch ( Exception e ) {
e . printStackTrace ( ) ;
}
2020-11-25 15:01:11 +00:00
}
} catch ( JedisConnectionException exception ) {
exception . printStackTrace ( ) ;
}
}
2020-05-10 06:52:06 +00:00
2020-12-23 17:04:23 +00:00
private void setupChannels ( Configuration config ) {
List < String > channels = config . getStringList ( " Channels " ) ;
channelsInByte = new byte [ channels . size ( ) ] [ 1 ] ;
for ( int x = 0 ; x < channels . size ( ) ; x + + ) {
channelsInByte [ x ] = channels . get ( x ) . getBytes ( StandardCharsets . UTF_8 ) ;
2020-12-03 11:07:27 +00:00
}
2020-10-23 13:49:25 +00:00
}
2020-05-10 19:10:45 +00:00
2020-12-23 17:04:23 +00:00
public Boolean isRedisConnectionOffline ( ) {
return isConnectionBroken . get ( ) ;
2020-06-28 14:40:53 +00:00
}
}