disconnecting bridges works again

also refactored the configuration
This commit is contained in:
Nikky Ai 2018-01-23 00:41:33 +01:00
parent a70c54f804
commit 932c98da69
8 changed files with 183 additions and 189 deletions

View File

@ -7,7 +7,6 @@ import civilengineering.eventhandlers.ChatMessageHandler
import civilengineering.eventhandlers.DeathEventHandler import civilengineering.eventhandlers.DeathEventHandler
import civilengineering.eventhandlers.UserActionHandler import civilengineering.eventhandlers.UserActionHandler
import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.common.config.Configuration
import net.minecraftforge.fml.common.Mod import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.event.* import net.minecraftforge.fml.common.event.*
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
@ -15,7 +14,6 @@ import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.message.SimpleMessageFactory import org.apache.logging.log4j.message.SimpleMessageFactory
import org.apache.logging.log4j.simple.SimpleLogger import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil import org.apache.logging.log4j.util.PropertiesUtil
import java.io.File
import java.util.* import java.util.*
const val MODID = "civilengineering" const val MODID = "civilengineering"
@ -31,8 +29,6 @@ const val VERSION = "0.0.1"
modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter" modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter"
) )
object CivilEngineering { object CivilEngineering {
var config: Configuration = Configuration()
//create fake logger to get around Nullability //create fake logger to get around Nullability
var logger: Logger = SimpleLogger("", var logger: Logger = SimpleLogger("",
Level.OFF, Level.OFF,
@ -50,10 +46,7 @@ object CivilEngineering {
logger = event.modLog logger = event.modLog
logger.info("loading logger") logger.info("loading logger")
CivilEngineering.logger.info("Reading bridge blueprints...") CivilEngineeringConfig(event.modConfigurationDirectory)
val directory = event.modConfigurationDirectory
config = Configuration(File(directory.path, "CivilEngineering.cfg"))
Config.readConfig()
} }
@Mod.EventHandler @Mod.EventHandler
@ -63,9 +56,6 @@ object CivilEngineering {
@Mod.EventHandler @Mod.EventHandler
fun postInit(event: FMLPostInitializationEvent) { fun postInit(event: FMLPostInitializationEvent) {
if (config.hasChanged()) {
config.save()
}
// MinecraftForge.EVENT_BUS.register(ServerChatHelper::class.java) // MinecraftForge.EVENT_BUS.register(ServerChatHelper::class.java)
} }

View File

@ -0,0 +1,80 @@
package civilengineering
import net.minecraftforge.common.config.Configuration
import java.io.File
var cfg: CivilEngineeringConfig? = null
class CivilEngineeringConfig(file: File) {
private val CATEGORY_RELAY_OPTIONS = "relay"
private val CATEGORY_CONNECTION = "connection"
val relay: RelayOptions
val connect: ConnectOptions
data class RelayOptions(
val deathEvents: Boolean,
val advancements: Boolean,
val joinLeave: Boolean
)
data class ConnectOptions(
val url: String,
val authToken: String,
val gateway: String
)
init {
CivilEngineering.logger.info("Reading bridge blueprints...")
val config = Configuration(file.resolve("CivilEngineering.cfg"))
config.addCustomCategoryComment(CATEGORY_RELAY_OPTIONS, "Relay options")
config.addCustomCategoryComment(CATEGORY_CONNECTION, "Connection settings")
relay = RelayOptions(
deathEvents = config.getBoolean(
"deathEvents",
CATEGORY_RELAY_OPTIONS,
false,
"Relay player death messages"
),
advancements = config.getBoolean(
"advancements",
CATEGORY_RELAY_OPTIONS,
false,
"Relay player advancements [NOT IMPLEMENTED]"
),
joinLeave = config.getBoolean(
"joinLeave",
CATEGORY_RELAY_OPTIONS,
false,
"Relay when a player joins or leaves the game [NOT IMPLEMENTED]"
)
)
connect = ConnectOptions(
url = config.getString(
"connectURL",
CATEGORY_CONNECTION,
"http://example.com:1234",
"The URL or IP address of the bridge server"
),
authToken = config.getString(
"authToken",
CATEGORY_CONNECTION,
"",
"Auth token used to connect to the bridge server"
),
gateway = config.getString(
"gateway",
CATEGORY_CONNECTION,
"minecraft",
"MatterBridge gateway"
)
)
if (config.hasChanged()) config.save()
cfg = this
}
}

View File

@ -1,76 +0,0 @@
package civilengineering
import net.minecraftforge.common.config.Configuration
import org.apache.logging.log4j.Level
object Config {
private val CATEGORY_RELAY_OPTIONS = "relay_options"
private val CATEGORY_CONNECTION = "connection"
var relayDeathEvents = false
var relayAdvancements = false //unused for now
var relayJoinLeave = false
var connectURL = "http://localhost"
var authToken = ""
var gateway = ""
fun readConfig() {
val config = CivilEngineering.config
try {
config.load()
initConfig(config)
} catch (e: Exception) {
CivilEngineering.logger!!.log(Level.ERROR, "Could not read config file!", e)
} finally {
if (config.hasChanged()) {
config.save()
}
}
}
private fun initConfig(cfg: Configuration) {
cfg.addCustomCategoryComment(CATEGORY_RELAY_OPTIONS, "Relay options")
cfg.addCustomCategoryComment(CATEGORY_CONNECTION, "Connection settings")
relayDeathEvents = cfg.getBoolean(
"relayDeathEvents",
CATEGORY_RELAY_OPTIONS,
false,
"Relay player death messages"
)
relayAdvancements = cfg.getBoolean(
"relayAdvancements",
CATEGORY_RELAY_OPTIONS,
false,
"Relay player advancements [NOT IMPLEMENTED]"
)
relayJoinLeave = cfg.getBoolean(
"relayJoinLeave",
CATEGORY_RELAY_OPTIONS,
false,
"Relay when a player joins or leaves the game [NOT IMPLEMENTED]"
)
connectURL = cfg.getString(
"connectURL",
CATEGORY_CONNECTION,
"http://example.com:1234",
"The URL or IP address of the bridge server, ex. http://example.com:1234"
)
authToken = cfg.getString(
"auth_token",
CATEGORY_CONNECTION,
"",
"Auth token used to connect to the bridge server"
)
gateway = cfg.getString(
"gateway",
CATEGORY_CONNECTION,
"",
"MatterBridge gateway"
)
}
}

View File

@ -1,12 +1,12 @@
package civilengineering.bridge package civilengineering.bridge
import civilengineering.Config import civilengineering.cfg
import com.google.gson.Gson import com.google.gson.Gson
data class ApiMessage( data class ApiMessage(
val username: String = "", val username: String = "",
val text: String = "", val text: String = "",
val gateway: String = Config.gateway, val gateway: String = cfg!!.connect.gateway,
val channel: String = "", val channel: String = "",
val userid: String = "", val userid: String = "",
val avatar: String = "", val avatar: String = "",
@ -17,10 +17,6 @@ data class ApiMessage(
val id: String = "" val id: String = ""
// val Extra: Any? = null // val Extra: Any? = null
) { ) {
fun encode(): String {
return gson.toJson(this)
}
companion object { companion object {
val gson = Gson() val gson = Gson()
@ -28,4 +24,8 @@ data class ApiMessage(
return gson.fromJson(json, ApiMessage::class.java) return gson.fromJson(json, ApiMessage::class.java)
} }
} }
fun encode(): String {
return gson.toJson(this)
}
} }

View File

@ -1,52 +0,0 @@
package civilengineering.bridge
import java.io.InputStreamReader
import java.net.HttpURLConnection
/**
* Created by nikky on 20/01/18.
* @author Nikky
* @version 1.0
*/
class CancellableConnectionFollowThread(httpConnClosure: () -> HttpURLConnection, private val mhandler: (String) -> Unit) : Thread() {
private val cancelGuard = Object()
private var waitingOnNetwork = true
var cancelled = false
private set
private val httpConn = httpConnClosure()
override fun run() {
try {
httpConn.allowUserInteraction = false
httpConn.instanceFollowRedirects = true
httpConn.requestMethod = "GET"
InputStreamReader(httpConn.inputStream).useLines {
it.forEach {
synchronized(cancelGuard) {
if (cancelled) return
waitingOnNetwork = false
}
mhandler(it)
synchronized(cancelGuard) {
if (cancelled) return
waitingOnNetwork = true
}
}
}
} catch (e: Exception) {
} finally {
httpConn.disconnect()
}
}
fun abort() {
synchronized(cancelGuard) {
httpConn.disconnect()
cancelled = true
if (waitingOnNetwork) stop()
}
join()
}
}

View File

@ -0,0 +1,61 @@
package civilengineering.bridge;
import civilengineering.CivilEngineering
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.HttpClients
import java.io.InputStream
import java.net.SocketException
val BUFFER_SIZE = 1000
class HttpStreamConnection(private val getClosure: () -> HttpGet, private val mhandler: (String) -> Unit) : Thread() {
private val client = HttpClients.createDefault()
private var stream: InputStream? = null
val get = getClosure()
var cancelled: Boolean = false
private set
override fun run() {
val response = client.execute(get)
val content = response.entity.content.buffered()
stream = content
//val reader = content.bufferedReader()
var buffer = ""
val buf = ByteArray(BUFFER_SIZE)
try {
while (!get.isAborted) {
val chars = content.read(buf)
CivilEngineering.logger.debug("incoming: $chars ")
if (chars > 0) {
buffer += String(buf.dropLast(buf.count() - chars).toByteArray())
CivilEngineering.logger.info(buffer)
while (buffer.contains("\n")) {
val line = buffer.substringBefore("\n")
buffer = buffer.substringAfter("\n")
mhandler(line)
}
} else if (chars < 0) {
break
}
}
} catch (e: SocketException) {
}
CivilEngineering.logger.info("closing stream")
content.close()
CivilEngineering.logger.info("thread finished")
return
}
fun close() {
cancelled = true
get.abort()
join()
println("killed thread")
}
}

View File

@ -1,24 +1,31 @@
package civilengineering.bridge package civilengineering.bridge
import civilengineering.CivilEngineering import civilengineering.CivilEngineering
import civilengineering.Config import civilengineering.cfg
import java.io.DataOutputStream import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.methods.HttpRequestBase
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.HttpClients
import java.io.IOException import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
object MessageHandler { object MessageHandler {
private fun createThread(): CancellableConnectionFollowThread { fun HttpRequestBase.authorize() {
if (cfg!!.connect.authToken.isNotEmpty() && getHeaders("Authorization").isEmpty())
setHeader("Authorization", "Bearer " + cfg!!.connect.authToken)
}
private fun createThread(): HttpStreamConnection {
CivilEngineering.logger.info("building bridge") CivilEngineering.logger.info("building bridge")
return CancellableConnectionFollowThread( return HttpStreamConnection(
{ {
CivilEngineering.logger.info("Connecting to bridge server @ " + Config.connectURL) HttpGet(cfg!!.connect.url + "/api/stream").apply {
val httpConn = URL(Config.connectURL + "/api/stream").openConnection() as HttpURLConnection authorize()
if (Config.authToken.isNotBlank()) }
httpConn.setRequestProperty("Authorization", "Bearer ${Config.authToken}")
httpConn
}, },
{ {
rcvQueue.add( rcvQueue.add(
@ -29,9 +36,7 @@ object MessageHandler {
) )
} }
private var cancellableThread: CancellableConnectionFollowThread = createThread() private var streamConnection: HttpStreamConnection = createThread()
private var xmitQueue = ConcurrentLinkedQueue<ApiMessage>()
var rcvQueue = ConcurrentLinkedQueue<ApiMessage>() var rcvQueue = ConcurrentLinkedQueue<ApiMessage>()
@ -43,16 +48,17 @@ object MessageHandler {
fun stop() { fun stop() {
CivilEngineering.logger.info("bridge closing") CivilEngineering.logger.info("bridge closing")
// MessageHandler.transmit(ApiMessage(text="bridge closing", username="Server")) // MessageHandler.transmit(ApiMessage(text="bridge closing", username="Server"))
cancellableThread.abort() streamConnection.close()
CivilEngineering.logger.info("bridge closed") CivilEngineering.logger.info("bridge closed")
} }
fun start(): Boolean { fun start(): Boolean {
if (cancellableThread.cancelled) { if (streamConnection.cancelled) {
cancellableThread = createThread() streamConnection = createThread()
} }
if (!cancellableThread.isAlive) { if (!streamConnection.isAlive) {
cancellableThread.start() streamConnection.start()
// MessageHandler.transmit(ApiMessage(text="bridge connected", username="Server")) // MessageHandler.transmit(ApiMessage(text="bridge connected", username="Server"))
return true return true
} }
@ -62,31 +68,16 @@ object MessageHandler {
@Throws(IOException::class) @Throws(IOException::class)
private fun transmitMessage(message: ApiMessage) { private fun transmitMessage(message: ApiMessage) {
//open a connection //open a connection
val url = URL(Config.connectURL + "/api/message") val client = HttpClients.createDefault()
val urlConnection = url.openConnection() val post = HttpPost(cfg!!.connect.url + "/api/message")
val connection = urlConnection as HttpURLConnection
//configure the connection post.entity = StringEntity(message.encode(), ContentType.APPLICATION_JSON)
connection.allowUserInteraction = false post.authorize()
connection.instanceFollowRedirects = true
connection.setRequestProperty("Content-Type", "application/json")
connection.requestMethod = "POST"
if (Config.authToken.isNotEmpty()) {
connection.setRequestProperty("Authorization", "Bearer " + Config.authToken)
}
//encode the ApiMessage for sending val response = client.execute(post)
val json = message.encode() val code = response.statusLine.statusCode
if (code != 200) {
//send the message CivilEngineering.logger.error("Server returned $code for $post")
connection.doOutput = true
val post = DataOutputStream(connection.outputStream)
post.writeBytes(json)
post.flush()
post.close()
if (connection.responseCode != 200) {
CivilEngineering.logger.error("Server returned " + connection.responseCode)
} }
} }
} }

View File

@ -1,8 +1,8 @@
package civilengineering.eventhandlers package civilengineering.eventhandlers
import civilengineering.Config
import civilengineering.bridge.ApiMessage import civilengineering.bridge.ApiMessage
import civilengineering.bridge.MessageHandler import civilengineering.bridge.MessageHandler
import civilengineering.cfg
import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayer
import net.minecraftforge.event.entity.living.LivingDeathEvent import net.minecraftforge.event.entity.living.LivingDeathEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
@ -10,7 +10,7 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
class DeathEventHandler { class DeathEventHandler {
@SubscribeEvent @SubscribeEvent
fun handleLivingDeathEvent(event: LivingDeathEvent) { fun handleLivingDeathEvent(event: LivingDeathEvent) {
if (Config.relayDeathEvents) { if (cfg!!.relay.deathEvents) {
val entity = event.entityLiving val entity = event.entityLiving
if (entity is EntityPlayer) { if (entity is EntityPlayer) {
val message = entity.getCombatTracker().deathMessage.unformattedText val message = entity.getCombatTracker().deathMessage.unformattedText