Create base error handler

This commit is contained in:
2020-07-02 21:47:05 +02:00
parent 5f1e459977
commit 7f2c328bc4
6 changed files with 93 additions and 64 deletions

View File

@@ -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(", ")
}
}

View File

@@ -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)
})
}

View File

@@ -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"
}

View File

@@ -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"
}
}

View File

@@ -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)
}

View File

@@ -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>