Create base error handler
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
package com.bartlomiejpluta.ttsserver.core.lua.loader
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import com.bartlomiejpluta.ttsserver.core.lua.sandbox.SandboxFactory
|
||||
import com.bartlomiejpluta.ttsserver.core.web.endpoint.DefaultEndpoint
|
||||
import com.bartlomiejpluta.ttsserver.core.web.endpoint.Endpoint
|
||||
import com.bartlomiejpluta.ttsserver.core.web.endpoint.QueuedEndpoint
|
||||
import com.bartlomiejpluta.ttsserver.core.web.uri.UriTemplate
|
||||
import com.bartlomiejpluta.ttsserver.ui.main.MainActivity
|
||||
import fi.iki.elonen.NanoHTTPD.Method
|
||||
import org.luaj.vm2.LuaClosure
|
||||
import org.luaj.vm2.LuaNil
|
||||
import org.luaj.vm2.LuaString
|
||||
import org.luaj.vm2.LuaError
|
||||
import org.luaj.vm2.LuaTable
|
||||
import java.io.File
|
||||
|
||||
class EndpointLoader(
|
||||
private val context: Context,
|
||||
@@ -20,16 +22,33 @@ class EndpointLoader(
|
||||
fun loadEndpoints(): List<Endpoint> {
|
||||
val scripts = context.getExternalFilesDir("Endpoints")?.listFiles() ?: emptyArray()
|
||||
|
||||
return scripts
|
||||
.map { sandboxFactory.createSandbox().loadfile(it.absolutePath).call() }
|
||||
.map {
|
||||
it as? LuaTable
|
||||
?: throw IllegalArgumentException("Expected single table to be returned")
|
||||
}
|
||||
.filter { parseEnabled(it) }
|
||||
.map { createEndpoint(it) }
|
||||
return scripts.mapNotNull { loadEndpoint(it) }
|
||||
}
|
||||
|
||||
private fun loadEndpoint(script: File): Endpoint? {
|
||||
try {
|
||||
return sandboxFactory
|
||||
.createSandbox()
|
||||
.loadfile(script.absolutePath)
|
||||
.call()
|
||||
.checktable()
|
||||
.takeIf { parseEnabled(it) }
|
||||
?.let { createEndpoint(it) }
|
||||
} catch (e: LuaError) {
|
||||
handleError(e)
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleError(exception: LuaError) = LocalBroadcastManager
|
||||
.getInstance(context)
|
||||
.sendBroadcast(Intent(MainActivity.LUA_ERROR).also {
|
||||
it.putExtra(MainActivity.MESSAGE, exception.message)
|
||||
})
|
||||
|
||||
|
||||
private fun createEndpoint(luaTable: LuaTable) = when (parseQueued(luaTable)) {
|
||||
false -> createDefaultEndpoint(luaTable)
|
||||
true -> createQueuedEndpoint(luaTable)
|
||||
@@ -49,43 +68,22 @@ class EndpointLoader(
|
||||
consumer = parseConsumer(luaTable)
|
||||
)
|
||||
|
||||
private fun parseUri(luaTable: LuaTable) = luaTable.get("uri")
|
||||
.takeIf { it !is LuaNil }
|
||||
?.let { it as? LuaString ?: throw IllegalArgumentException("'uri' must be of string type'") }
|
||||
?.tojstring()
|
||||
?.let { UriTemplate.parse(it) }
|
||||
?: throw IllegalArgumentException("'uri' field is required")
|
||||
private fun parseUri(luaTable: LuaTable) = luaTable.get("uri").checkjstring()
|
||||
.let { UriTemplate.parse(it) }
|
||||
|
||||
private fun parseConsumer(luaTable: LuaTable) = luaTable.get("consumer")
|
||||
.takeIf { it !is LuaNil }
|
||||
?.let {
|
||||
it as? LuaClosure ?: throw IllegalArgumentException("'consumer' must be a function'")
|
||||
}
|
||||
?: throw IllegalArgumentException("'consumer' field is required")
|
||||
private fun parseConsumer(luaTable: LuaTable) = luaTable.get("consumer").checkclosure()
|
||||
|
||||
private fun parseEnabled(luaTable: LuaTable) = luaTable.get("enabled")
|
||||
.takeIf { it !is LuaNil }
|
||||
?.checkboolean()
|
||||
?: true
|
||||
private fun parseEnabled(luaTable: LuaTable) = luaTable.get("enabled").optboolean(true)
|
||||
|
||||
private fun parseAccepts(luaTable: LuaTable) = luaTable.get("accepts")
|
||||
.takeIf { it !is LuaNil }
|
||||
?.let {
|
||||
it as? LuaString ?: throw IllegalArgumentException("'accepts' must be of string type'")
|
||||
}
|
||||
?.tojstring()
|
||||
?: "text/plain"
|
||||
private fun parseAccepts(luaTable: LuaTable) = luaTable.get("accepts").optjstring("text/plain")
|
||||
|
||||
private fun parseQueued(luaTable: LuaTable) = luaTable.get("queued")
|
||||
.takeIf { it !is LuaNil }
|
||||
?.checkboolean()
|
||||
?: false
|
||||
private fun parseQueued(luaTable: LuaTable) = luaTable.get("queued").optboolean(false)
|
||||
|
||||
private fun parseMethod(luaTable: LuaTable) = luaTable.get("method")
|
||||
.takeIf { it !is LuaNil }
|
||||
?.let {
|
||||
it as? LuaString ?: throw IllegalArgumentException("'method' must be of string type'")
|
||||
}
|
||||
?.let { Method.valueOf(it.tojstring()) }
|
||||
?: Method.GET
|
||||
private fun parseMethod(luaTable: LuaTable) = luaTable.get("method").optjstring(Method.GET.name)
|
||||
.let { method -> Method.values().firstOrNull { it.name == method } }
|
||||
?: throw LuaError("Invalid HTTP method. Allowed methods are: $ALLOWED_METHODS")
|
||||
|
||||
companion object {
|
||||
private val ALLOWED_METHODS = Method.values().joinToString(", ")
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,16 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import com.bartlomiejpluta.ttsserver.core.sonos.queue.SonosQueue
|
||||
import com.bartlomiejpluta.ttsserver.core.tts.engine.TTSEngine
|
||||
import com.bartlomiejpluta.ttsserver.core.tts.status.TTSStatus
|
||||
import com.bartlomiejpluta.ttsserver.core.web.endpoint.DefaultEndpoint
|
||||
import com.bartlomiejpluta.ttsserver.core.web.endpoint.Endpoint
|
||||
import com.bartlomiejpluta.ttsserver.core.web.endpoint.QueuedEndpoint
|
||||
import com.bartlomiejpluta.ttsserver.core.web.endpoint.Worker
|
||||
import com.bartlomiejpluta.ttsserver.core.web.exception.WebException
|
||||
import com.bartlomiejpluta.ttsserver.service.foreground.ForegroundService
|
||||
import com.bartlomiejpluta.ttsserver.service.state.ServiceState
|
||||
import com.bartlomiejpluta.ttsserver.ui.main.MainActivity
|
||||
import com.bartlomiejpluta.ttsserver.ui.preference.key.PreferenceKey
|
||||
import com.bartlomiejpluta.ttsserver.ui.preference.model.TimeRange
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
import fi.iki.elonen.NanoHTTPD.Response.Status.*
|
||||
import org.json.JSONObject
|
||||
import org.luaj.vm2.LuaError
|
||||
|
||||
|
||||
class WebServer(
|
||||
@@ -56,6 +54,8 @@ class WebServer(
|
||||
throw WebException(BAD_REQUEST, "Unknown error")
|
||||
} catch (e: WebException) {
|
||||
return handleWebException(e)
|
||||
} catch (e: LuaError) {
|
||||
return handleLuaError(e)
|
||||
} catch (e: Exception) {
|
||||
return handleUnknownException(e)
|
||||
}
|
||||
@@ -78,6 +78,9 @@ class WebServer(
|
||||
private fun handleWebException(e: WebException) =
|
||||
newFixedLengthResponse(e.status, MIME_JSON, e.json)
|
||||
|
||||
private fun handleLuaError(e: LuaError) =
|
||||
newFixedLengthResponse(INTERNAL_ERROR, MIME_PLAINTEXT, e.message)
|
||||
|
||||
private fun handleUnknownException(e: Exception): Response {
|
||||
val stacktrace = when (preferences.getBoolean(PreferenceKey.ENABLE_HTTP_DEBUG, false)) {
|
||||
true -> e.toString()
|
||||
@@ -211,8 +214,8 @@ class WebServer(
|
||||
|
||||
LocalBroadcastManager
|
||||
.getInstance(context)
|
||||
.sendBroadcast(Intent(ForegroundService.CHANGE_STATE).also {
|
||||
it.putExtra(ForegroundService.STATE, ServiceState.RUNNING.name)
|
||||
.sendBroadcast(Intent(MainActivity.CHANGE_STATE).also {
|
||||
it.putExtra(MainActivity.STATE, ServiceState.RUNNING.name)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -223,8 +226,8 @@ class WebServer(
|
||||
|
||||
LocalBroadcastManager
|
||||
.getInstance(context)
|
||||
.sendBroadcast(Intent(ForegroundService.CHANGE_STATE).also {
|
||||
it.putExtra(ForegroundService.STATE, ServiceState.STOPPED.name)
|
||||
.sendBroadcast(Intent(MainActivity.CHANGE_STATE).also {
|
||||
it.putExtra(MainActivity.STATE, ServiceState.STOPPED.name)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -86,8 +86,6 @@ class ForegroundService : DaggerService() {
|
||||
var state = ServiceState.STOPPED
|
||||
|
||||
private const val WAKELOCK_TAG = "ForegroundService::lock"
|
||||
const val CHANGE_STATE = "com.bartlomiejpluta.ttsserver.service.CHANGE_STATE"
|
||||
const val STATE = "STATE"
|
||||
const val START = "START"
|
||||
const val STOP = "STOP"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bartlomiejpluta.ttsserver.ui.main
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.*
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -34,13 +35,26 @@ class MainActivity : DaggerAppCompatActivity() {
|
||||
lateinit var networkUtil: NetworkUtil
|
||||
|
||||
private val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
(intent?.getStringExtra(ForegroundService.STATE) ?: ServiceState.STOPPED.name)
|
||||
.let { ServiceState.valueOf(it) }
|
||||
.let { updateViewAccordingToServiceState(it) }
|
||||
override fun onReceive(context: Context?, intent: Intent?) = when(intent?.action) {
|
||||
MainActivity.CHANGE_STATE -> dispatchChangeStateIntent(intent)
|
||||
LUA_ERROR -> dispatchLuaErrorIntent(intent)
|
||||
else -> throw UnsupportedOperationException("This action is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
private fun dispatchChangeStateIntent(intent: Intent) {
|
||||
(intent.getStringExtra(MainActivity.STATE) ?: ServiceState.STOPPED.name)
|
||||
.let { ServiceState.valueOf(it) }
|
||||
.let { updateViewAccordingToServiceState(it) }
|
||||
}
|
||||
|
||||
private fun dispatchLuaErrorIntent(intent: Intent) = AlertDialog.Builder(this)
|
||||
.setTitle(R.string.error_title)
|
||||
.setMessage(intent.getStringExtra(MESSAGE))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
.create()
|
||||
.show()
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
return true
|
||||
@@ -82,9 +96,14 @@ class MainActivity : DaggerAppCompatActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(MainActivity.CHANGE_STATE)
|
||||
addAction(LUA_ERROR)
|
||||
}
|
||||
|
||||
LocalBroadcastManager
|
||||
.getInstance(this)
|
||||
.registerReceiver(receiver, IntentFilter(ForegroundService.CHANGE_STATE))
|
||||
.registerReceiver(receiver, filter)
|
||||
updateViewAccordingToServiceState(ForegroundService.state)
|
||||
}
|
||||
|
||||
@@ -114,4 +133,11 @@ class MainActivity : DaggerAppCompatActivity() {
|
||||
startService(it)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANGE_STATE = "com.bartlomiejpluta.ttsserver.service.CHANGE_STATE"
|
||||
const val LUA_ERROR = "com.bartlomiejpluta.ttsserver.service.LUA_ERROR"
|
||||
const val MESSAGE = "message"
|
||||
const val STATE = "STATE"
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,10 @@ import com.bartlomiejpluta.R
|
||||
import com.bartlomiejpluta.ttsserver.core.web.mime.MimeType
|
||||
import com.bartlomiejpluta.ttsserver.service.foreground.ForegroundService
|
||||
import com.bartlomiejpluta.ttsserver.service.state.ServiceState
|
||||
import com.bartlomiejpluta.ttsserver.ui.preference.model.TimeRange
|
||||
import com.bartlomiejpluta.ttsserver.ui.preference.key.PreferenceKey
|
||||
import com.bartlomiejpluta.ttsserver.ui.main.MainActivity
|
||||
import com.bartlomiejpluta.ttsserver.ui.preference.custom.IntEditTextPreference
|
||||
import com.bartlomiejpluta.ttsserver.ui.preference.key.PreferenceKey
|
||||
import com.bartlomiejpluta.ttsserver.ui.preference.model.TimeRange
|
||||
|
||||
|
||||
class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
@@ -35,7 +36,7 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
(intent?.getStringExtra(ForegroundService.STATE) ?: ServiceState.STOPPED.name)
|
||||
(intent?.getStringExtra(MainActivity.STATE) ?: ServiceState.STOPPED.name)
|
||||
.let { ServiceState.valueOf(it) }
|
||||
.let { updateViewAccordingToServiceState(it) }
|
||||
}
|
||||
@@ -51,7 +52,7 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
||||
super.onResume()
|
||||
LocalBroadcastManager
|
||||
.getInstance(requireContext())
|
||||
.registerReceiver(receiver, IntentFilter(ForegroundService.CHANGE_STATE))
|
||||
.registerReceiver(receiver, IntentFilter(MainActivity.CHANGE_STATE))
|
||||
updateViewAccordingToServiceState(ForegroundService.state)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,4 +45,7 @@
|
||||
|
||||
<string name="menu_settings">Settings</string>
|
||||
<string name="menu_help">Help</string>
|
||||
|
||||
<string name="error_title">Error</string>
|
||||
<string name="error_invalid_endpoint_script">Skipping %1$s file because of error:\n%2$s</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user