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 ;
import org.bukkit.ChatColor ;
2020-06-28 14:40:53 +00:00
import org.bukkit.configuration.Configuration ;
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-06-08 13:53:23 +00:00
import redis.clients.jedis.BinaryJedis ;
import redis.clients.jedis.BinaryJedisPubSub ;
2020-06-28 14:40:53 +00:00
import redis.clients.jedis.JedisPool ;
import redis.clients.jedis.JedisPoolConfig ;
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-28 14:40:53 +00:00
import java.util.concurrent.ExecutorService ;
import java.util.concurrent.Executors ;
2020-06-14 13:43:46 +00:00
import java.util.concurrent.atomic.AtomicBoolean ;
2020-05-10 06:52:06 +00:00
2020-10-23 11:34:30 +00:00
public class RedisManager extends BinaryJedisPubSub implements Runnable {
2020-05-10 19:10:45 +00:00
2020-11-03 10:23:00 +00:00
private ExecutorService RedisReconnector ;
2020-10-22 10:30:41 +00:00
private RediSkript plugin ;
2020-05-10 19:10:45 +00:00
2020-06-28 14:40:53 +00:00
private JedisPool jedisPool ;
private ExecutorService RedisService ;
//sub
private BinaryJedis subscribeJedis ;
2020-06-08 13:53:23 +00:00
private List < String > channels ;
2020-06-14 13:43:46 +00:00
private AtomicBoolean isShuttingDown = new AtomicBoolean ( false ) ;
2020-06-28 14:40:53 +00:00
private Encryption encryption ;
2020-06-14 13:43:46 +00:00
2020-05-10 19:10:45 +00:00
2020-10-22 10:30:41 +00:00
public RedisManager ( RediSkript plugin ) {
2020-05-10 06:52:06 +00:00
this . plugin = plugin ;
2020-06-28 14:40:53 +00:00
Configuration config = this . plugin . getConfig ( ) ;
JedisPoolConfig JConfig = new JedisPoolConfig ( ) ;
2020-10-24 10:29:49 +00:00
int maxConnections = config . getInt ( " Redis.MaxConnections " ) ;
if ( maxConnections < 2 ) { maxConnections = 2 ; }
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-11-03 09:29:06 +00:00
RedisReconnector = Executors . newSingleThreadExecutor ( ) ;
RedisService = Executors . newSingleThreadExecutor ( ) ;
2020-07-28 06:44:00 +00:00
try {
this . subscribeJedis = this . jedisPool . getResource ( ) ;
} catch ( Exception ignored ) {
}
2020-06-28 14:40:53 +00:00
this . channels = config . getStringList ( " Channels " ) ;
encryption = new Encryption ( config ) ;
}
2020-07-28 06:44:00 +00:00
public void start ( ) {
2020-11-03 09:29:06 +00:00
this . RedisReconnector . execute ( this ) ;
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-11-25 11:18:23 +00:00
while ( ! isShuttingDown . get ( ) & & plugin . isEnabled ( ) ) {
2020-05-10 19:10:45 +00:00
try {
2020-10-31 14:48:42 +00:00
plugin . getLogger ( ) . info ( ChatColor . translateAlternateColorCodes ( '&' , " &cConnecting to redis... " ) ) ;
2020-06-28 14:40:53 +00:00
if ( ! this . subscribeJedis . isConnected ( ) ) this . subscribeJedis = this . jedisPool . getResource ( ) ;
2020-10-31 14:48:42 +00:00
plugin . getLogger ( ) . info ( ChatColor . translateAlternateColorCodes ( '&' , " &aRedis connected! " ) ) ;
2020-06-26 13:02:39 +00:00
int byteArr2dSize = 1 ;
byte [ ] [ ] channelsInByte = new byte [ channels . size ( ) ] [ byteArr2dSize ] ;
boolean reInitializeByteArray ;
// Loop that reInitialize array IF array size is not enough
do {
reInitializeByteArray = false ;
try {
/* Data Initialization for channelsInByte array from List<String> channels */
for ( int x = 0 ; x < channels . size ( ) ; x + + ) {
2020-10-22 11:19:29 +00:00
channelsInByte [ x ] = channels . get ( x ) . getBytes ( StandardCharsets . UTF_8 ) ;
2020-06-26 13:02:39 +00:00
}
} catch ( ArrayIndexOutOfBoundsException ex ) {
reInitializeByteArray = true ;
/* Increase the current 2d array size to increase 1 and reinitialize the array*/
byteArr2dSize + = 1 ;
channelsInByte = new byte [ channels . size ( ) ] [ byteArr2dSize ] ;
}
} while ( reInitializeByteArray ) ;
2020-06-28 14:40:53 +00:00
this . subscribeJedis . subscribe ( this , channelsInByte ) ;
2020-10-24 10:29:49 +00:00
2020-06-26 13:02:39 +00:00
} catch ( Exception e ) {
2020-11-25 15:01:11 +00:00
if ( isShuttingDown . get ( ) | | ! plugin . isEnabled ( ) ) {
return ;
}
2020-10-31 14:48:42 +00:00
plugin . getLogger ( ) . warning ( ChatColor . translateAlternateColorCodes ( '&' , " &cConnection to redis has failed! &cReconnecting... " ) ) ;
2020-07-28 06:44:00 +00:00
if ( this . subscribeJedis ! = null ) {
this . subscribeJedis . close ( ) ;
}
2020-06-14 13:43:46 +00:00
}
try {
Thread . sleep ( 1000 ) ;
} catch ( InterruptedException e ) {
e . printStackTrace ( ) ;
2020-05-10 19:10:45 +00:00
}
}
2020-05-10 06:52:06 +00:00
}
@Override
2020-06-08 13:53:23 +00:00
public void onMessage ( byte [ ] channel , byte [ ] message ) {
2020-10-22 10:30:41 +00:00
String channelString = new String ( channel , StandardCharsets . UTF_8 ) ;
2020-10-22 14:44:45 +00:00
String receivedMessage = null ;
2020-05-10 06:52:06 +00:00
try {
2020-10-22 09:32:08 +00:00
//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 ( ) ;
}
} 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 ) ;
2020-11-25 15:01:11 +00:00
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-25 11:53:52 +00:00
}
2020-11-25 15:01:11 +00:00
} else if ( j . get ( " Type " ) . equals ( " SkriptVariables " ) ) {
2020-11-26 15:05:07 +00:00
//Transfer variables between servers
2020-11-03 09:29:06 +00:00
2020-11-26 15:05:07 +00:00
JSONArray variableNames = j . getJSONArray ( " Names " ) ;
Object inputValue ;
String changeValue = null ;
JSONArray variableValues = null ;
if ( ! j . isNull ( " Values " ) ) {
variableValues = j . getJSONArray ( " Values " ) ;
2020-11-25 15:01:11 +00:00
}
for ( int i = 0 ; i < variableNames . length ( ) ; i + + ) {
2020-11-26 15:05:07 +00:00
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 ) ;
}
2020-11-25 15:01:11 +00:00
} else {
2020-11-26 15:05:07 +00:00
if ( ! variableValues . isNull ( i ) ) {
changeValue = variableValues . get ( i ) . toString ( ) ;
}
String [ ] inputs = changeValue . split ( " \\ ^ " , 2 ) ;
inputValue = Classes . deserialize ( inputs [ 0 ] , Base64 . getDecoder ( ) . decode ( inputs [ 1 ] ) ) ;
2020-11-28 08:02:14 +00:00
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
}
}
}
2020-06-08 13:53:23 +00:00
}
2020-05-10 06:52:06 +00:00
} catch ( Exception e ) {
e . printStackTrace ( ) ;
2020-10-31 14:48:42 +00:00
Bukkit . getLogger ( ) . warning ( ChatColor . translateAlternateColorCodes ( '&' , " &cI got a message that was empty from channel " + channelString + " please check your code that you used to send the message. Message content: " ) ) ;
2020-10-22 14:44:45 +00:00
Bukkit . getLogger ( ) . warning ( receivedMessage ) ;
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-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 ;
if ( this . getEncryption ( ) . isEncryptionEnabled ( ) ) {
message = this . getEncryption ( ) . encrypt ( json . toString ( ) ) ;
} else {
message = json . toString ( ) . getBytes ( StandardCharsets . UTF_8 ) ;
}
//execute sending of redis message on the main thread if plugin is disabling
//so it can still process the sending
//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 ( ) ) {
this . getRedisService ( ) . execute ( ( ) - > {
BinaryJedis j = this . getJedisPool ( ) . getResource ( ) ;
j . publish ( channel . getBytes ( StandardCharsets . UTF_8 ) , message ) ;
j . close ( ) ;
} ) ;
} else {
BinaryJedis j = this . getJedisPool ( ) . getResource ( ) ;
j . publish ( channel . getBytes ( StandardCharsets . UTF_8 ) , message ) ;
j . close ( ) ;
}
} catch ( JedisConnectionException exception ) {
exception . printStackTrace ( ) ;
}
}
2020-05-10 06:52:06 +00:00
2020-06-26 13:02:39 +00:00
public void shutdown ( ) {
2020-06-14 13:43:46 +00:00
this . isShuttingDown . set ( true ) ;
2020-07-28 06:44:00 +00:00
if ( this . subscribeJedis ! = null ) {
2020-07-06 18:38:54 +00:00
this . unsubscribe ( ) ;
this . subscribeJedis . close ( ) ;
2020-10-24 14:59:48 +00:00
this . subscribeJedis . getClient ( ) . close ( ) ;
2020-10-23 14:48:22 +00:00
this . jedisPool . getResource ( ) . close ( ) ;
2020-07-06 18:38:54 +00:00
}
2020-11-25 11:18:23 +00:00
isShuttingDown . set ( true ) ;
2020-11-03 09:29:06 +00:00
this . RedisReconnector . shutdown ( ) ;
2020-06-28 14:40:53 +00:00
this . RedisService . shutdown ( ) ;
2020-11-03 10:23:00 +00:00
this . RedisService = null ;
this . RedisReconnector = null ;
2020-07-06 18:38:54 +00:00
2020-05-10 06:52:06 +00:00
}
2020-10-23 13:49:25 +00:00
public void reload ( ) {
2020-10-24 09:32:40 +00:00
this . shutdown ( ) ;
plugin . startRedis ( true ) ;
2020-10-23 13:49:25 +00:00
}
2020-05-10 19:10:45 +00:00
2020-06-28 14:40:53 +00:00
public JedisPool getJedisPool ( ) {
return jedisPool ;
}
2020-10-23 11:34:30 +00:00
public Encryption getEncryption ( ) {
return encryption ;
2020-07-28 06:44:00 +00:00
}
2020-11-02 12:14:38 +00:00
public ExecutorService getRedisService ( ) { return RedisService ; }
2020-06-28 14:40:53 +00:00
}