move api into core
This commit is contained in:
parent
e30e8132ca
commit
7818afc920
|
@ -0,0 +1,92 @@
|
|||
package matterlink.api
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
/**
|
||||
* Created by nikky on 07/05/18.
|
||||
*
|
||||
* @author Nikky
|
||||
* @version 1.0
|
||||
*/
|
||||
class ApiMessage (
|
||||
username: String? = null,
|
||||
text: String? = null,
|
||||
gateway: String? = null,
|
||||
channel: String? = null,
|
||||
userid: String? = null,
|
||||
avatar: String? = null,
|
||||
account: String? = null,
|
||||
protocol: String? = null,
|
||||
event: String? = null,
|
||||
id: String? = null
|
||||
) {
|
||||
@SerializedName("username") private var _username: String? = username
|
||||
@SerializedName("text") private var _text: String? = text
|
||||
@SerializedName("gateway") private var _gateway: String? = gateway
|
||||
@SerializedName("channel") private var _channel: String? = channel
|
||||
@SerializedName("userid") private var _userid: String? = userid
|
||||
@SerializedName("avatar") private var _avatar: String? = avatar
|
||||
@SerializedName("account") private var _account: String? = account
|
||||
@SerializedName("protocol") private var _protocol: String? = protocol
|
||||
@SerializedName("event") private var _event: String? = event
|
||||
@SerializedName("id") private var _id: String? = id
|
||||
|
||||
var username: String
|
||||
get() = _username ?: ""
|
||||
set(username) { this._username = username }
|
||||
|
||||
var text: String
|
||||
get() = _text ?: ""
|
||||
set(text) { this._text = text }
|
||||
|
||||
var gateway: String
|
||||
get() = _gateway ?: ""
|
||||
set(gateway) { this._gateway = gateway }
|
||||
|
||||
var channel: String
|
||||
get() = _channel ?: ""
|
||||
set(channel) { this._channel = channel }
|
||||
|
||||
var userid: String
|
||||
get() = _userid ?: ""
|
||||
set(userid) { this._userid = userid }
|
||||
|
||||
var avatar: String
|
||||
get() = _avatar ?: ""
|
||||
set(avatar) { this._avatar = avatar }
|
||||
|
||||
var account: String
|
||||
get() = _account ?: ""
|
||||
set(account) { this._account = account }
|
||||
|
||||
var protocol: String
|
||||
get() = _protocol ?: ""
|
||||
set(protocol) { this._protocol = protocol }
|
||||
|
||||
var event: String
|
||||
get() = _event ?: ""
|
||||
set(event) { this._event = event }
|
||||
|
||||
var id: String
|
||||
get() = _id ?: ""
|
||||
set(id) { this._id = id }
|
||||
|
||||
fun encode(): String {
|
||||
return gson.toJson(this)
|
||||
}
|
||||
|
||||
override fun toString(): String = encode()
|
||||
|
||||
companion object {
|
||||
val USER_ACTION = "user_action"
|
||||
val JOIN_LEAVE = "join_leave"
|
||||
|
||||
private val gson = GsonBuilder()
|
||||
.create()
|
||||
|
||||
fun decode(json: String): ApiMessage {
|
||||
return gson.fromJson(json, ApiMessage::class.java)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package matterlink.api
|
||||
|
||||
data class Config (
|
||||
var url: String = "",
|
||||
var token: String = "",
|
||||
var announceConnect: Boolean = true,
|
||||
var announceDisconnect: Boolean = true,
|
||||
var reconnectWait: Long = 500,
|
||||
var systemUser: String = "Server"
|
||||
)
|
||||
{
|
||||
fun sync(connection: StreamConnection) {
|
||||
connection.token = token
|
||||
connection.host = url
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
package matterlink.api
|
||||
|
||||
import org.apache.logging.log4j.Logger
|
||||
import java.io.BufferedReader
|
||||
import java.io.DataOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.MalformedURLException
|
||||
import java.net.ProtocolException
|
||||
import java.net.URL
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
/**
|
||||
* Created by nikky on 07/05/18.
|
||||
*
|
||||
* @author Nikky
|
||||
* @version 1.0
|
||||
*/
|
||||
open class MessageHandler {
|
||||
private var enabled = false
|
||||
|
||||
private var connectErrors = 0
|
||||
private var reconnectCooldown = 0
|
||||
private var sendErrors = 0
|
||||
|
||||
var config: Config = Config()
|
||||
set(value) {
|
||||
field = value.apply {
|
||||
sync(streamConnection)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: make callbacks: onConnect onDisconnect onError etc
|
||||
|
||||
var queue: ConcurrentLinkedQueue<ApiMessage> = ConcurrentLinkedQueue()
|
||||
private set
|
||||
private var streamConnection: StreamConnection = StreamConnection(queue)
|
||||
|
||||
var logger: Logger
|
||||
get() = streamConnection.logger
|
||||
set(l) {
|
||||
streamConnection.logger = l
|
||||
}
|
||||
|
||||
|
||||
private var nextCheck: Long = 0
|
||||
|
||||
init {
|
||||
streamConnection.addOnSuccess { success ->
|
||||
if (success) {
|
||||
logger.info("connected successfully")
|
||||
connectErrors = 0
|
||||
reconnectCooldown = 0
|
||||
} else {
|
||||
reconnectCooldown = connectErrors
|
||||
connectErrors++
|
||||
logger.error(String.format("connectErrors: %d", connectErrors))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop(message: String? = null) {
|
||||
if (message != null && config.announceDisconnect) {
|
||||
sendStatusUpdate(message)
|
||||
}
|
||||
enabled = false
|
||||
streamConnection.close()
|
||||
}
|
||||
|
||||
|
||||
fun start(message: String?, clear: Boolean) {
|
||||
config.sync(streamConnection)
|
||||
if (clear) {
|
||||
clear()
|
||||
}
|
||||
|
||||
enabled = true
|
||||
streamConnection.open()
|
||||
|
||||
if (message != null && config.announceConnect) {
|
||||
sendStatusUpdate(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun clear() {
|
||||
try {
|
||||
val url = URL(config.url + "/api/messages")
|
||||
val conn = url.openConnection() as HttpURLConnection
|
||||
|
||||
if (!config.token.isEmpty()) {
|
||||
val bearerAuth = "Bearer " + config.token
|
||||
conn.setRequestProperty("Authorization", bearerAuth)
|
||||
}
|
||||
|
||||
conn.requestMethod = "GET"
|
||||
|
||||
BufferedReader(InputStreamReader(conn.inputStream)).forEachLine { line ->
|
||||
logger.trace("skipping $line")
|
||||
}
|
||||
} catch (e: MalformedURLException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: ProtocolException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open fun sendStatusUpdate(message: String) {
|
||||
transmit(ApiMessage(text = message))
|
||||
}
|
||||
|
||||
open fun transmit(msg: ApiMessage) {
|
||||
if (streamConnection.isConnected || streamConnection.isConnecting) {
|
||||
if (msg.username.isEmpty())
|
||||
msg.username = config.systemUser
|
||||
if (msg.gateway.isEmpty()) {
|
||||
logger.fatal("missing gateway on message: $msg")
|
||||
return
|
||||
}
|
||||
logger.debug("Transmitting: $msg")
|
||||
transmitMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
private fun transmitMessage(message: ApiMessage) {
|
||||
try {
|
||||
val url = URL(config.url + "/api/message")
|
||||
val conn = url.openConnection() as HttpURLConnection
|
||||
|
||||
if (!config.token.isEmpty()) {
|
||||
val bearerAuth = "Bearer " + config.token
|
||||
conn.setRequestProperty("Authorization", bearerAuth)
|
||||
}
|
||||
|
||||
val postData = message.encode()
|
||||
logger.trace(postData)
|
||||
|
||||
conn.requestMethod = "POST"
|
||||
conn.setRequestProperty("Content-Type", "application/json")
|
||||
conn.setRequestProperty("charset", "utf-8")
|
||||
conn.setRequestProperty("Content-Length", "" + postData.toByteArray().size)
|
||||
conn.doOutput = true
|
||||
conn.doInput = true
|
||||
|
||||
DataOutputStream(conn.outputStream).use { wr -> wr.write(postData.toByteArray()) }
|
||||
|
||||
// conn.getInputStream().close();
|
||||
conn.connect()
|
||||
val code = conn.responseCode
|
||||
if (code != 200) {
|
||||
logger.error("Server returned $code")
|
||||
sendErrors++
|
||||
if (sendErrors > 5) {
|
||||
logger.error("Interrupting Connection to matterbridge API due to status code $code")
|
||||
stop()
|
||||
}
|
||||
} else {
|
||||
sendErrors = 0
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
logger.error("sending message caused $e")
|
||||
sendErrors++
|
||||
if (sendErrors > 5) {
|
||||
logger.error("Caught too many errors, closing bridge")
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* clll this method every tick / cycle to make sure it is reconnecting
|
||||
*/
|
||||
fun checkConnection() {
|
||||
if (enabled && !streamConnection.isConnected && !streamConnection.isConnecting) {
|
||||
logger.trace("check connection")
|
||||
logger.trace("next: $nextCheck")
|
||||
logger.trace("now: " + System.currentTimeMillis())
|
||||
if (nextCheck > System.currentTimeMillis()) return
|
||||
nextCheck = System.currentTimeMillis() + config.reconnectWait
|
||||
|
||||
if (connectErrors >= 10) {
|
||||
logger.error("Caught too many errors, closing bridge")
|
||||
stop("Interrupting connection to matterbridge API due to accumulated connection errors")
|
||||
return
|
||||
}
|
||||
|
||||
if (reconnectCooldown <= 0) {
|
||||
logger.info("Trying to reconnect")
|
||||
start("Reconnecting to matterbridge API after connection error", false)
|
||||
} else {
|
||||
reconnectCooldown--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package matterlink.api
|
||||
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.ConnectException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import java.util.Arrays
|
||||
import java.util.LinkedList
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
/**
|
||||
* Created by nikky on 07/05/18.
|
||||
*
|
||||
* @author Nikky
|
||||
* @version 1.0
|
||||
*/
|
||||
class StreamConnection(private val rcvQueue: ConcurrentLinkedQueue<ApiMessage>) : Runnable {
|
||||
private var thread: Thread = createThread()
|
||||
private var urlConnection: HttpURLConnection? = null
|
||||
private val onSuccessCallbacks = LinkedList<(Boolean) -> Unit>()
|
||||
|
||||
var logger = LogManager.getLogger("matterlink.api")
|
||||
var host = ""
|
||||
var token = ""
|
||||
|
||||
var isConnected = false
|
||||
private set
|
||||
var isConnecting = false
|
||||
private set
|
||||
var isCancelled = false
|
||||
private set
|
||||
|
||||
private fun createThread(): Thread {
|
||||
val thread = Thread(this)
|
||||
thread.name = "RcvThread"
|
||||
return thread
|
||||
}
|
||||
|
||||
fun addOnSuccess(callback: (Boolean) -> Unit) {
|
||||
onSuccessCallbacks.add(callback)
|
||||
}
|
||||
|
||||
fun removeOnSuccess(callback: (Boolean) -> Unit) {
|
||||
onSuccessCallbacks.remove(callback)
|
||||
}
|
||||
|
||||
private fun onSuccess(success: Boolean) {
|
||||
isConnecting = false
|
||||
isConnected = success
|
||||
for (callback in onSuccessCallbacks) {
|
||||
callback(success)
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
try {
|
||||
val serviceURL = "$host/api/stream"
|
||||
val myURL: URL
|
||||
|
||||
myURL = URL(serviceURL)
|
||||
urlConnection = myURL.openConnection() as HttpURLConnection
|
||||
urlConnection!!.requestMethod = "GET"
|
||||
if (!token.isEmpty()) {
|
||||
val bearerAuth = "Bearer $token"
|
||||
urlConnection!!.setRequestProperty("Authorization", bearerAuth)
|
||||
}
|
||||
try {
|
||||
urlConnection!!.inputStream.use { input ->
|
||||
logger.info("connection opened")
|
||||
onSuccess(true)
|
||||
// BufferedInputStream bufferedInput = new BufferedInputStream(input, 8 * 1024);
|
||||
val buffer = StringBuilder()
|
||||
while (!isCancelled) {
|
||||
val buf = ByteArray(1024)
|
||||
Thread.sleep(10)
|
||||
while (input.available() <= 0) {
|
||||
if (isCancelled) break
|
||||
Thread.sleep(10)
|
||||
}
|
||||
val chars = input.read(buf)
|
||||
|
||||
logger.trace( String.format("read %d chars", chars))
|
||||
if (chars > 0) {
|
||||
val added = String(Arrays.copyOfRange(buf, 0, chars))
|
||||
logger.debug("DEBUG", "json: $added")
|
||||
buffer.append(added)
|
||||
while (buffer.toString().contains("\n")) {
|
||||
val index = buffer.indexOf("\n")
|
||||
val line = buffer.substring(0, index)
|
||||
buffer.delete(0, index + 1)
|
||||
rcvQueue.add(ApiMessage.decode(line))
|
||||
}
|
||||
} else if (chars < 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
onClose()
|
||||
}
|
||||
} catch (e: MalformedURLException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: ConnectException) {
|
||||
e.printStackTrace()
|
||||
onSuccess(false)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
onSuccess(false)
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun onClose() {
|
||||
logger.info("Bridge connection closed!")
|
||||
isConnected = false
|
||||
isConnecting = false
|
||||
}
|
||||
|
||||
fun open() {
|
||||
if (!thread.isAlive) {
|
||||
thread = createThread()
|
||||
isConnecting = true
|
||||
isCancelled = false
|
||||
thread.start()
|
||||
logger.info("Starting Connection")
|
||||
}
|
||||
if (thread.isAlive) {
|
||||
logger.info("Bridge is connecting")
|
||||
}
|
||||
}
|
||||
|
||||
fun close() {
|
||||
try {
|
||||
isCancelled = true
|
||||
if (urlConnection != null) {
|
||||
urlConnection!!.disconnect()
|
||||
}
|
||||
thread.join()
|
||||
logger.info("Thread stopped")
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue