Create in-app logging framework
This commit is contained in:
@@ -27,6 +27,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
def room_version = "2.2.5"
|
||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
|
||||||
@@ -41,6 +44,9 @@ dependencies {
|
|||||||
implementation 'com.google.dagger:dagger-android-support:2.15'
|
implementation 'com.google.dagger:dagger-android-support:2.15'
|
||||||
implementation 'com.github.adrielcafe:AndroidAudioConverter:0.0.8'
|
implementation 'com.github.adrielcafe:AndroidAudioConverter:0.0.8'
|
||||||
implementation 'org.luaj:luaj-jse:3.0.1'
|
implementation 'org.luaj:luaj-jse:3.0.1'
|
||||||
|
implementation "org.threeten:threetenbp:1.4.4"
|
||||||
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
kapt 'com.google.dagger:dagger-android-processor:2.15'
|
kapt 'com.google.dagger:dagger-android-processor:2.15'
|
||||||
kapt 'com.google.dagger:dagger-compiler:2.15'
|
kapt 'com.google.dagger:dagger-compiler:2.15'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="com.bartlomiejpluta.ttsserver.TTSApplication"
|
android:name=".TTSApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@@ -27,25 +27,29 @@
|
|||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="m">
|
tools:targetApi="m">
|
||||||
<activity
|
<activity
|
||||||
android:name="com.bartlomiejpluta.ttsserver.ui.help.HelpActivity"
|
android:name=".ui.log.LogActivity"
|
||||||
android:parentActivityName="com.bartlomiejpluta.ttsserver.ui.main.MainActivity" />
|
android:parentActivityName=".ui.main.MainActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.bartlomiejpluta.ttsserver.ui.preference.component.PreferencesActivity"
|
android:name=".ui.help.HelpActivity"
|
||||||
|
android:parentActivityName=".ui.main.MainActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.preference.component.PreferencesActivity"
|
||||||
android:label="@string/title_activity_preferences"
|
android:label="@string/title_activity_preferences"
|
||||||
android:parentActivityName="com.bartlomiejpluta.ttsserver.ui.main.MainActivity" />
|
android:parentActivityName=".ui.main.MainActivity" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="com.bartlomiejpluta.ttsserver.service.foreground.ForegroundService"
|
android:name=".service.foreground.ForegroundService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="com.bartlomiejpluta.permission.TTS_HTTP_SERVICE" />
|
android:permission="com.bartlomiejpluta.permission.TTS_HTTP_SERVICE" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.bartlomiejpluta.ttsserver.ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.bartlomiejpluta.ttsserver.common.db.converter
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import org.threeten.bp.Instant
|
||||||
|
|
||||||
|
object InstantConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
@JvmStatic
|
||||||
|
fun toInstant(epoch: Long?) = epoch?.let { Instant.ofEpochMilli(it) }
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
@JvmStatic
|
||||||
|
fun fromInstant(instant: Instant?) = instant?.toEpochMilli()
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.bartlomiejpluta.ttsserver.core.log.converter
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.model.enumeration.LogLevel
|
||||||
|
|
||||||
|
object LogLevelConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
@JvmStatic
|
||||||
|
fun toLogLevel(string: String?) = string?.let { LogLevel.valueOf(it) }
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
@JvmStatic
|
||||||
|
fun fromLogLevel(logLevel: LogLevel?) = logLevel?.name
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.bartlomiejpluta.ttsserver.core.log.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.model.entity.LogEntry
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface LogDao {
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insert(entry: LogEntry)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM logentry ORDER BY date DESC LIMIT 5000")
|
||||||
|
fun getAll(): List<LogEntry>
|
||||||
|
|
||||||
|
@Query("DELETE FROM logentry")
|
||||||
|
fun clear()
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.bartlomiejpluta.ttsserver.core.log.database
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.dao.LogDao
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.model.entity.LogEntry
|
||||||
|
|
||||||
|
@Database(entities = [LogEntry::class], version = 1)
|
||||||
|
abstract class LogDatabase : RoomDatabase() {
|
||||||
|
abstract fun dao(): LogDao
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.bartlomiejpluta.ttsserver.core.log.model.entity
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import com.bartlomiejpluta.ttsserver.common.db.converter.InstantConverter
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.converter.LogLevelConverter
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.model.enumeration.LogLevel
|
||||||
|
import org.threeten.bp.Instant
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@TypeConverters(InstantConverter::class, LogLevelConverter::class)
|
||||||
|
data class LogEntry (
|
||||||
|
@PrimaryKey
|
||||||
|
val id: Int?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "date")
|
||||||
|
val date: Instant,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "level")
|
||||||
|
val level: LogLevel,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "source")
|
||||||
|
val source: String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.bartlomiejpluta.ttsserver.core.log.model.enumeration
|
||||||
|
|
||||||
|
enum class LogLevel {
|
||||||
|
DEBUG,
|
||||||
|
INFO,
|
||||||
|
WARN,
|
||||||
|
ERROR,
|
||||||
|
FATAL
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.bartlomiejpluta.ttsserver.core.log.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.database.LogDatabase
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.model.entity.LogEntry
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.model.enumeration.LogLevel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.threeten.bp.Instant
|
||||||
|
|
||||||
|
class LogService(private val context: Context, logDatabase: LogDatabase) {
|
||||||
|
private val dao = logDatabase.dao()
|
||||||
|
private val broadcastManager = LocalBroadcastManager.getInstance(context)
|
||||||
|
|
||||||
|
fun debug(source: String, message: String) = log(LogLevel.DEBUG, source, message)
|
||||||
|
|
||||||
|
fun info(source: String, message: String) = log(LogLevel.INFO, source, message)
|
||||||
|
|
||||||
|
fun warn(source: String, message: String) = log(LogLevel.WARN, source, message)
|
||||||
|
|
||||||
|
fun error(source: String, message: String) = log(LogLevel.ERROR, source, message)
|
||||||
|
|
||||||
|
fun fatal(source: String, message: String) = log(LogLevel.FATAL, source, message)
|
||||||
|
|
||||||
|
fun log(level: LogLevel, source: String, message: String) =
|
||||||
|
log(LogEntry(null, Instant.now(), level, source, message))
|
||||||
|
|
||||||
|
fun log(logEntry: LogEntry) {
|
||||||
|
persistLogEntry(logEntry)
|
||||||
|
broadcastLogEntry()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun persistLogEntry(logEntry: LogEntry) = runBlocking {
|
||||||
|
withContext(Dispatchers.IO + Job()) {
|
||||||
|
dao.insert(logEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun broadcastLogEntry() = broadcastManager.sendBroadcast(Intent(UPDATE_LOGS))
|
||||||
|
|
||||||
|
val allLogs: List<LogEntry>
|
||||||
|
get() = dao.getAll()
|
||||||
|
|
||||||
|
fun clearLogs() = dao.clear()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val UPDATE_LOGS = "UPDATE_LOGS"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.bartlomiejpluta.ttsserver.core.lua.lib
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.model.enumeration.LogLevel
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.service.LogService
|
||||||
|
import org.luaj.vm2.LuaValue
|
||||||
|
import org.luaj.vm2.lib.OneArgFunction
|
||||||
|
import org.luaj.vm2.lib.TwoArgFunction
|
||||||
|
|
||||||
|
class LogLibrary(private val logService: LogService) : TwoArgFunction() {
|
||||||
|
override fun call(modname: LuaValue, env: LuaValue): LuaValue {
|
||||||
|
val scriptName = env.get("_G").get("script").checkjstring()
|
||||||
|
val log = LuaValue.tableOf()
|
||||||
|
log.set("debug", LogFunction(logService, LogLevel.DEBUG, scriptName))
|
||||||
|
log.set("info", LogFunction(logService, LogLevel.INFO, scriptName))
|
||||||
|
log.set("warn", LogFunction(logService, LogLevel.WARN, scriptName))
|
||||||
|
log.set("error", LogFunction(logService, LogLevel.ERROR, scriptName))
|
||||||
|
log.set("fatal", LogFunction(logService, LogLevel.FATAL, scriptName))
|
||||||
|
env.set("log", log)
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogFunction(
|
||||||
|
private val log: LogService,
|
||||||
|
private val level: LogLevel,
|
||||||
|
private val scriptName: String
|
||||||
|
) : OneArgFunction() {
|
||||||
|
override fun call(message: LuaValue): LuaValue {
|
||||||
|
log.log(level, scriptName, message.checkjstring())
|
||||||
|
return LuaValue.NIL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,12 +8,18 @@ import org.luaj.vm2.lib.TwoArgFunction
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class ConfigLoader(context: Context) {
|
class ConfigLoader(context: Context) {
|
||||||
private val configDirectory = context.getExternalFilesDir("config")
|
private val configFile = File(context.getExternalFilesDir("config"), "config.lua")
|
||||||
|
private var cachedConfig: LuaTable? = null
|
||||||
|
|
||||||
|
fun refreshConfig(env: Globals) {
|
||||||
|
cachedConfig = env.loadfile(configFile.absolutePath).call().checktable()
|
||||||
|
}
|
||||||
|
|
||||||
fun loadConfig(env: Globals) {
|
fun loadConfig(env: Globals) {
|
||||||
val configFile = File(configDirectory, "config.lua")
|
cachedConfig
|
||||||
val table = env.loadfile(configFile.absolutePath).call().checktable()
|
?.let { ConfigLibrary(it) }
|
||||||
env.load(ConfigLibrary(table))
|
?.let { env.load(it) }
|
||||||
|
?: error("Config has not been refreshed before loading start")
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigLibrary(private val table: LuaTable) : TwoArgFunction() {
|
class ConfigLibrary(private val table: LuaTable) : TwoArgFunction() {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import com.bartlomiejpluta.ttsserver.R
|
import com.bartlomiejpluta.ttsserver.R
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.service.LogService
|
||||||
import com.bartlomiejpluta.ttsserver.core.lua.sandbox.SandboxFactory
|
import com.bartlomiejpluta.ttsserver.core.lua.sandbox.SandboxFactory
|
||||||
import com.bartlomiejpluta.ttsserver.core.web.endpoint.DefaultEndpoint
|
import com.bartlomiejpluta.ttsserver.core.web.endpoint.DefaultEndpoint
|
||||||
import com.bartlomiejpluta.ttsserver.core.web.endpoint.Endpoint
|
import com.bartlomiejpluta.ttsserver.core.web.endpoint.Endpoint
|
||||||
@@ -19,28 +20,37 @@ import java.io.File
|
|||||||
class EndpointLoader(
|
class EndpointLoader(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val sandboxFactory: SandboxFactory,
|
private val sandboxFactory: SandboxFactory,
|
||||||
private val tasksQueueFactory: TasksQueueFactory
|
private val tasksQueueFactory: TasksQueueFactory,
|
||||||
|
private val log: LogService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun loadEndpoints(): List<Endpoint> {
|
fun loadEndpoints(): List<Endpoint> {
|
||||||
|
sandboxFactory.refreshConfig()
|
||||||
|
log.info(TAG, "Loading endpoint scripts...")
|
||||||
val scripts = context.getExternalFilesDir("endpoints")?.listFiles() ?: emptyArray()
|
val scripts = context.getExternalFilesDir("endpoints")?.listFiles() ?: emptyArray()
|
||||||
|
|
||||||
return scripts.mapNotNull { loadEndpoint(it) }
|
return scripts
|
||||||
|
.mapNotNull { loadEndpoint(it) }
|
||||||
|
.also { log.info(TAG, "Loading endpoints is complete") }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadEndpoint(script: File): Endpoint? {
|
private fun loadEndpoint(script: File): Endpoint? {
|
||||||
try {
|
try {
|
||||||
|
log.info(TAG, "Loading ${script.name} script...")
|
||||||
return sandboxFactory
|
return sandboxFactory
|
||||||
.createSandbox()
|
.createSandbox(script.name)
|
||||||
.loadfile(script.absolutePath)
|
.loadfile(script.absolutePath)
|
||||||
.call()
|
.call()
|
||||||
.checktable()
|
.checktable()
|
||||||
.takeIf { parseEnabled(it) }
|
.takeIf { parseEnabled(it) }
|
||||||
?.let { createEndpoint(it) }
|
?.let { createEndpoint(it) }
|
||||||
|
?.also { log.info(TAG, "Script ${script.name} has been loaded successfully") }
|
||||||
} catch (e: LuaError) {
|
} catch (e: LuaError) {
|
||||||
handleError(e)
|
handleError(e)
|
||||||
|
log.error(TAG, "Loading script ${script.name} failed: ${e.message}")
|
||||||
return null
|
return null
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
log.error(TAG, "Loading script ${script.name} failed: ${e.message}")
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,6 +101,7 @@ class EndpointLoader(
|
|||||||
?: throw LuaError("Invalid HTTP method. Allowed methods are: $ALLOWED_METHODS")
|
?: throw LuaError("Invalid HTTP method. Allowed methods are: $ALLOWED_METHODS")
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val TAG = "@endpoints"
|
||||||
private val ALLOWED_METHODS = Method.values().joinToString(", ")
|
private val ALLOWED_METHODS = Method.values().joinToString(", ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.bartlomiejpluta.ttsserver.core.lua.sandbox
|
package com.bartlomiejpluta.ttsserver.core.lua.sandbox
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.service.LogService
|
||||||
import com.bartlomiejpluta.ttsserver.core.lua.lib.*
|
import com.bartlomiejpluta.ttsserver.core.lua.lib.*
|
||||||
import com.bartlomiejpluta.ttsserver.core.lua.loader.ConfigLoader
|
import com.bartlomiejpluta.ttsserver.core.lua.loader.ConfigLoader
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -18,21 +19,31 @@ import org.luaj.vm2.lib.jse.JseOsLib
|
|||||||
|
|
||||||
class SandboxFactory(
|
class SandboxFactory(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
private val log: LogService,
|
||||||
private val configLoader: ConfigLoader,
|
private val configLoader: ConfigLoader,
|
||||||
private val threadLibrary: ThreadLibrary,
|
private val threadLibrary: ThreadLibrary,
|
||||||
private val serverLibrary: ServerLibrary,
|
private val serverLibrary: ServerLibrary,
|
||||||
|
private val logLibrary: LogLibrary,
|
||||||
private val httpLibrary: HTTPLibrary,
|
private val httpLibrary: HTTPLibrary,
|
||||||
private val ttsLibrary: TTSLibrary,
|
private val ttsLibrary: TTSLibrary,
|
||||||
private val sonosLibrary: SonosLibrary
|
private val sonosLibrary: SonosLibrary
|
||||||
) {
|
) {
|
||||||
fun createSandbox() = runBlocking {
|
fun createSandbox(scriptName: String) = createLuaGlobals(scriptName).also {
|
||||||
|
loadConfiguration(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshConfig() = runBlocking {
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
createLuaGlobals()
|
log.info(TAG, "Loading configuration file")
|
||||||
|
val configEnvironment = createLuaGlobals(":config")
|
||||||
|
configLoader.refreshConfig(configEnvironment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createLuaGlobals() = Globals().also { sandbox ->
|
private fun createLuaGlobals(scriptName: String) = Globals().also { sandbox ->
|
||||||
|
log.info(TAG, "Installing sandbox for $scriptName...")
|
||||||
loadStandardLibraries(sandbox)
|
loadStandardLibraries(sandbox)
|
||||||
|
sandbox.get("_G").checktable().set("script", scriptName)
|
||||||
loadApplicationLibraries(sandbox)
|
loadApplicationLibraries(sandbox)
|
||||||
install(sandbox)
|
install(sandbox)
|
||||||
loadLuaLibraries(sandbox)
|
loadLuaLibraries(sandbox)
|
||||||
@@ -49,6 +60,7 @@ class SandboxFactory(
|
|||||||
|
|
||||||
private fun loadApplicationLibraries(sandbox: Globals) {
|
private fun loadApplicationLibraries(sandbox: Globals) {
|
||||||
sandbox.load(serverLibrary)
|
sandbox.load(serverLibrary)
|
||||||
|
sandbox.load(logLibrary)
|
||||||
sandbox.load(threadLibrary)
|
sandbox.load(threadLibrary)
|
||||||
sandbox.load(httpLibrary)
|
sandbox.load(httpLibrary)
|
||||||
sandbox.load(ttsLibrary)
|
sandbox.load(ttsLibrary)
|
||||||
@@ -58,6 +70,9 @@ class SandboxFactory(
|
|||||||
private fun install(sandbox: Globals) {
|
private fun install(sandbox: Globals) {
|
||||||
LoadState.install(sandbox)
|
LoadState.install(sandbox)
|
||||||
LuaC.install(sandbox)
|
LuaC.install(sandbox)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadConfiguration(sandbox: Globals) {
|
||||||
configLoader.loadConfig(sandbox)
|
configLoader.loadConfig(sandbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,4 +83,8 @@ class SandboxFactory(
|
|||||||
?.map { (name, reader) -> name.substringBeforeLast(".") to sandbox.load(reader, name) }
|
?.map { (name, reader) -> name.substringBeforeLast(".") to sandbox.load(reader, name) }
|
||||||
?.forEach { (name, value) -> sandbox.set(name, value.call()) }
|
?.forEach { (name, value) -> sandbox.set(name, value.call()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "@sandbox"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.service.LogService
|
||||||
import com.bartlomiejpluta.ttsserver.core.tts.engine.TTSEngine
|
import com.bartlomiejpluta.ttsserver.core.tts.engine.TTSEngine
|
||||||
import com.bartlomiejpluta.ttsserver.core.tts.status.TTSStatus
|
import com.bartlomiejpluta.ttsserver.core.tts.status.TTSStatus
|
||||||
import com.bartlomiejpluta.ttsserver.core.web.endpoint.Endpoint
|
import com.bartlomiejpluta.ttsserver.core.web.endpoint.Endpoint
|
||||||
@@ -22,7 +23,8 @@ class WebServer(
|
|||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val preferences: SharedPreferences,
|
private val preferences: SharedPreferences,
|
||||||
private val tts: TTSEngine,
|
private val tts: TTSEngine,
|
||||||
private val endpoints: List<Endpoint>
|
private val endpoints: List<Endpoint>,
|
||||||
|
private val log: LogService
|
||||||
) : NanoHTTPD(port) {
|
) : NanoHTTPD(port) {
|
||||||
private val queuedEndpoints = endpoints.mapNotNull { it as? QueuedEndpoint }
|
private val queuedEndpoints = endpoints.mapNotNull { it as? QueuedEndpoint }
|
||||||
|
|
||||||
@@ -36,10 +38,13 @@ class WebServer(
|
|||||||
|
|
||||||
throw WebException(BAD_REQUEST, "Unknown error")
|
throw WebException(BAD_REQUEST, "Unknown error")
|
||||||
} catch (e: WebException) {
|
} catch (e: WebException) {
|
||||||
|
log.error(TAG, "Web exception occurred: ${e.message}")
|
||||||
return handleWebException(e)
|
return handleWebException(e)
|
||||||
} catch (e: LuaError) {
|
} catch (e: LuaError) {
|
||||||
|
log.error(TAG, "Lua error occurred: ${e.message}")
|
||||||
return handleLuaError(e)
|
return handleLuaError(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
log.fatal(TAG, "Unknown error occurred: ${e.message}")
|
||||||
return handleUnknownException(e)
|
return handleUnknownException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,6 +80,7 @@ class WebServer(
|
|||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
super.start()
|
super.start()
|
||||||
|
log.info(TAG, "Web server is up")
|
||||||
|
|
||||||
LocalBroadcastManager
|
LocalBroadcastManager
|
||||||
.getInstance(context)
|
.getInstance(context)
|
||||||
@@ -84,6 +90,7 @@ class WebServer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
|
log.info(TAG, "Stopping web server...")
|
||||||
super.stop()
|
super.stop()
|
||||||
queuedEndpoints.forEach { it.shutdownQueue() }
|
queuedEndpoints.forEach { it.shutdownQueue() }
|
||||||
|
|
||||||
@@ -95,6 +102,7 @@ class WebServer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "@webserver"
|
||||||
private const val MIME_JSON = "application/json"
|
private const val MIME_JSON = "application/json"
|
||||||
const val DEFAULT_PORT = 8080
|
const val DEFAULT_PORT = 8080
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.bartlomiejpluta.ttsserver.core.web.server
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.service.LogService
|
||||||
import com.bartlomiejpluta.ttsserver.core.lua.loader.EndpointLoader
|
import com.bartlomiejpluta.ttsserver.core.lua.loader.EndpointLoader
|
||||||
import com.bartlomiejpluta.ttsserver.core.tts.engine.TTSEngine
|
import com.bartlomiejpluta.ttsserver.core.tts.engine.TTSEngine
|
||||||
import com.bartlomiejpluta.ttsserver.ui.preference.key.PreferenceKey
|
import com.bartlomiejpluta.ttsserver.ui.preference.key.PreferenceKey
|
||||||
@@ -10,13 +11,15 @@ class WebServerFactory(
|
|||||||
private val preferences: SharedPreferences,
|
private val preferences: SharedPreferences,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val tts: TTSEngine,
|
private val tts: TTSEngine,
|
||||||
private val endpointLoader: EndpointLoader
|
private val endpointLoader: EndpointLoader,
|
||||||
|
private val logService: LogService
|
||||||
) {
|
) {
|
||||||
fun createWebServer() = WebServer(
|
fun createWebServer() = WebServer(
|
||||||
preferences.getInt(PreferenceKey.PORT, WebServer.DEFAULT_PORT),
|
preferences.getInt(PreferenceKey.PORT, WebServer.DEFAULT_PORT),
|
||||||
context,
|
context,
|
||||||
preferences,
|
preferences,
|
||||||
tts,
|
tts,
|
||||||
endpointLoader.loadEndpoints()
|
endpointLoader.loadEndpoints(),
|
||||||
|
logService
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.bartlomiejpluta.ttsserver.di.module
|
|||||||
|
|
||||||
import com.bartlomiejpluta.ttsserver.service.foreground.ForegroundService
|
import com.bartlomiejpluta.ttsserver.service.foreground.ForegroundService
|
||||||
import com.bartlomiejpluta.ttsserver.ui.help.HelpActivity
|
import com.bartlomiejpluta.ttsserver.ui.help.HelpActivity
|
||||||
|
import com.bartlomiejpluta.ttsserver.ui.log.LogActivity
|
||||||
import com.bartlomiejpluta.ttsserver.ui.main.MainActivity
|
import com.bartlomiejpluta.ttsserver.ui.main.MainActivity
|
||||||
import com.bartlomiejpluta.ttsserver.ui.preference.component.PreferencesActivity
|
import com.bartlomiejpluta.ttsserver.ui.preference.component.PreferencesActivity
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
@@ -19,6 +20,9 @@ abstract class AndroidModule {
|
|||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun preferencesActivity(): PreferencesActivity
|
abstract fun preferencesActivity(): PreferencesActivity
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract fun logActivity(): LogActivity
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun foregroundService(): ForegroundService
|
abstract fun foregroundService(): ForegroundService
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.bartlomiejpluta.ttsserver.di.module
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.service.LogService
|
||||||
import com.bartlomiejpluta.ttsserver.core.lua.lib.*
|
import com.bartlomiejpluta.ttsserver.core.lua.lib.*
|
||||||
import com.bartlomiejpluta.ttsserver.core.lua.loader.ConfigLoader
|
import com.bartlomiejpluta.ttsserver.core.lua.loader.ConfigLoader
|
||||||
import com.bartlomiejpluta.ttsserver.core.lua.loader.EndpointLoader
|
import com.bartlomiejpluta.ttsserver.core.lua.loader.EndpointLoader
|
||||||
@@ -22,9 +23,10 @@ class LuaModule {
|
|||||||
fun endpointLoader(
|
fun endpointLoader(
|
||||||
context: Context,
|
context: Context,
|
||||||
sandboxFactory: SandboxFactory,
|
sandboxFactory: SandboxFactory,
|
||||||
tasksQueueFactory: TasksQueueFactory
|
tasksQueueFactory: TasksQueueFactory,
|
||||||
|
logService: LogService
|
||||||
) =
|
) =
|
||||||
EndpointLoader(context, sandboxFactory, tasksQueueFactory)
|
EndpointLoader(context, sandboxFactory, tasksQueueFactory, logService)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@@ -34,17 +36,21 @@ class LuaModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
fun sandboxFactory(
|
fun sandboxFactory(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
logService: LogService,
|
||||||
configLoader: ConfigLoader,
|
configLoader: ConfigLoader,
|
||||||
threadLibrary: ThreadLibrary,
|
threadLibrary: ThreadLibrary,
|
||||||
serverLibrary: ServerLibrary,
|
serverLibrary: ServerLibrary,
|
||||||
|
logLibrary: LogLibrary,
|
||||||
httpLibrary: HTTPLibrary,
|
httpLibrary: HTTPLibrary,
|
||||||
ttsLibrary: TTSLibrary,
|
ttsLibrary: TTSLibrary,
|
||||||
sonosLibrary: SonosLibrary
|
sonosLibrary: SonosLibrary
|
||||||
) = SandboxFactory(
|
) = SandboxFactory(
|
||||||
context,
|
context,
|
||||||
|
logService,
|
||||||
configLoader,
|
configLoader,
|
||||||
threadLibrary,
|
threadLibrary,
|
||||||
serverLibrary,
|
serverLibrary,
|
||||||
|
logLibrary,
|
||||||
httpLibrary,
|
httpLibrary,
|
||||||
ttsLibrary,
|
ttsLibrary,
|
||||||
sonosLibrary
|
sonosLibrary
|
||||||
@@ -63,6 +69,10 @@ class LuaModule {
|
|||||||
fun serverLibrary(context: Context, networkUtil: NetworkUtil) =
|
fun serverLibrary(context: Context, networkUtil: NetworkUtil) =
|
||||||
ServerLibrary(context, networkUtil)
|
ServerLibrary(context, networkUtil)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun logLibrary(logService: LogService) = LogLibrary(logService)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun httpLibrary() = HTTPLibrary()
|
fun httpLibrary() = HTTPLibrary()
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import android.content.Context
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.speech.tts.TextToSpeech
|
import android.speech.tts.TextToSpeech
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.room.Room
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.database.LogDatabase
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.service.LogService
|
||||||
import com.bartlomiejpluta.ttsserver.core.lua.loader.EndpointLoader
|
import com.bartlomiejpluta.ttsserver.core.lua.loader.EndpointLoader
|
||||||
import com.bartlomiejpluta.ttsserver.core.tts.engine.TTSEngine
|
import com.bartlomiejpluta.ttsserver.core.tts.engine.TTSEngine
|
||||||
import com.bartlomiejpluta.ttsserver.core.tts.status.TTSStatusHolder
|
import com.bartlomiejpluta.ttsserver.core.tts.status.TTSStatusHolder
|
||||||
@@ -43,12 +46,14 @@ class TTSModule {
|
|||||||
preferences: SharedPreferences,
|
preferences: SharedPreferences,
|
||||||
context: Context,
|
context: Context,
|
||||||
tts: TTSEngine,
|
tts: TTSEngine,
|
||||||
endpointLoader: EndpointLoader
|
endpointLoader: EndpointLoader,
|
||||||
|
logService: LogService
|
||||||
) = WebServerFactory(
|
) = WebServerFactory(
|
||||||
preferences,
|
preferences,
|
||||||
context,
|
context,
|
||||||
tts,
|
tts,
|
||||||
endpointLoader
|
endpointLoader,
|
||||||
|
logService
|
||||||
)
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -70,4 +75,14 @@ class TTSModule {
|
|||||||
context: Context,
|
context: Context,
|
||||||
networkUtil: NetworkUtil
|
networkUtil: NetworkUtil
|
||||||
) = ForegroundNotificationFactory(context, networkUtil)
|
) = ForegroundNotificationFactory(context, networkUtil)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun logDatabase(context: Context) = Room
|
||||||
|
.databaseBuilder(context, LogDatabase::class.java, "log.db")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun logService(context: Context, logDatabase: LogDatabase) = LogService(context, logDatabase)
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.service.LogService
|
||||||
import com.bartlomiejpluta.ttsserver.core.web.server.WebServer
|
import com.bartlomiejpluta.ttsserver.core.web.server.WebServer
|
||||||
import com.bartlomiejpluta.ttsserver.core.web.server.WebServerFactory
|
import com.bartlomiejpluta.ttsserver.core.web.server.WebServerFactory
|
||||||
import com.bartlomiejpluta.ttsserver.service.notification.ForegroundNotificationFactory
|
import com.bartlomiejpluta.ttsserver.service.notification.ForegroundNotificationFactory
|
||||||
@@ -23,6 +24,9 @@ class ForegroundService : DaggerService() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var notificationFactory: ForegroundNotificationFactory
|
lateinit var notificationFactory: ForegroundNotificationFactory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var log: LogService
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
startForeground(1, notificationFactory.createForegroundNotification())
|
startForeground(1, notificationFactory.createForegroundNotification())
|
||||||
@@ -50,6 +54,7 @@ class ForegroundService : DaggerService() {
|
|||||||
private fun startService() {
|
private fun startService() {
|
||||||
if (isServiceStarted) return
|
if (isServiceStarted) return
|
||||||
isServiceStarted = true
|
isServiceStarted = true
|
||||||
|
log.info(TAG, "Starting service...")
|
||||||
wakeLock =
|
wakeLock =
|
||||||
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||||
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||||
@@ -62,10 +67,12 @@ class ForegroundService : DaggerService() {
|
|||||||
webServer?.let {
|
webServer?.let {
|
||||||
state = ServiceState.RUNNING
|
state = ServiceState.RUNNING
|
||||||
it.start()
|
it.start()
|
||||||
|
log.info(TAG, "Service started successfully")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopService() {
|
private fun stopService() {
|
||||||
|
log.info(TAG, "Stopping service...")
|
||||||
webServer?.stop()
|
webServer?.stop()
|
||||||
webServer = null
|
webServer = null
|
||||||
wakeLock?.let {
|
wakeLock?.let {
|
||||||
@@ -77,6 +84,7 @@ class ForegroundService : DaggerService() {
|
|||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
state = ServiceState.STOPPED
|
state = ServiceState.STOPPED
|
||||||
|
log.info(TAG, "Service stopped successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -85,6 +93,7 @@ class ForegroundService : DaggerService() {
|
|||||||
// than to place it as a static field
|
// than to place it as a static field
|
||||||
var state = ServiceState.STOPPED
|
var state = ServiceState.STOPPED
|
||||||
|
|
||||||
|
private const val TAG = "@service"
|
||||||
private const val WAKELOCK_TAG = "ForegroundService::lock"
|
private const val WAKELOCK_TAG = "ForegroundService::lock"
|
||||||
const val START = "START"
|
const val START = "START"
|
||||||
const val STOP = "STOP"
|
const val STOP = "STOP"
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package com.bartlomiejpluta.ttsserver.ui.log
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import com.bartlomiejpluta.ttsserver.R
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.model.entity.LogEntry
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.model.enumeration.LogLevel
|
||||||
|
import com.bartlomiejpluta.ttsserver.core.log.service.LogService
|
||||||
|
import dagger.android.support.DaggerAppCompatActivity
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.threeten.bp.ZoneId
|
||||||
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
|
import org.threeten.bp.format.FormatStyle
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
class LogActivity : DaggerAppCompatActivity() {
|
||||||
|
private val formatter = DateTimeFormatter
|
||||||
|
.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
||||||
|
.withLocale(Locale.getDefault())
|
||||||
|
.withZone(ZoneId.systemDefault())
|
||||||
|
|
||||||
|
private lateinit var logView: WebView
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var logService: LogService
|
||||||
|
|
||||||
|
private val receiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
when (intent?.action) {
|
||||||
|
LogService.UPDATE_LOGS -> updateLogs()
|
||||||
|
else -> throw UnsupportedOperationException("This action is not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_log)
|
||||||
|
logView = findViewById(R.id.logview)
|
||||||
|
logView.webViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageCommitVisible(view: WebView?, url: String?) {
|
||||||
|
view?.scrollY = view?.contentHeight ?: 0
|
||||||
|
//view?.pageDown(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
super.onResume()
|
||||||
|
val filter = IntentFilter().apply {
|
||||||
|
addAction(LogService.UPDATE_LOGS)
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalBroadcastManager
|
||||||
|
.getInstance(this)
|
||||||
|
.registerReceiver(receiver, filter)
|
||||||
|
|
||||||
|
updateLogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLogs() {
|
||||||
|
val logs = fetchLogs()
|
||||||
|
val logsHTML = logsToHTML(logs)
|
||||||
|
val html = LOGS_STYLES + logsHTML
|
||||||
|
logView.loadDataWithBaseURL("", html, "text/html", "UTF-8", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchLogs() = runBlocking {
|
||||||
|
withContext(Dispatchers.IO + Job()) {
|
||||||
|
logService.allLogs.asReversed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logsToHTML(logs: List<LogEntry>) = logs.joinToString("<br />") {
|
||||||
|
String.format(
|
||||||
|
"%s <span style=\"color: %s\">%s</span> [ %s ] %s",
|
||||||
|
formatter.format(it.date).padEnd(23, ' ').replace(" ", " "),
|
||||||
|
logLevelColor(it.level),
|
||||||
|
it.level.name.padEnd(5, ' ').replace(" ", " "),
|
||||||
|
it.source.padEnd(15, ' ').replace(" ", " "),
|
||||||
|
it.message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logLevelColor(level: LogLevel) = when (level) {
|
||||||
|
LogLevel.DEBUG -> "#0044FF"
|
||||||
|
LogLevel.INFO -> "#00FF00"
|
||||||
|
LogLevel.WARN -> "#FFEB3B"
|
||||||
|
LogLevel.ERROR -> "#FF7777"
|
||||||
|
LogLevel.FATAL -> "#FF0000"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
LocalBroadcastManager
|
||||||
|
.getInstance(this)
|
||||||
|
.unregisterReceiver(receiver)
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.menu_logs, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.clear_logs -> clearLogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearLogs() = AlertDialog.Builder(this)
|
||||||
|
.setTitle(getString(R.string.dialog_confirmation))
|
||||||
|
.setMessage(getString(R.string.dialog_clear_logs_confirmation))
|
||||||
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
runBlocking {
|
||||||
|
withContext(Dispatchers.IO + Job()) {
|
||||||
|
logService.clearLogs()
|
||||||
|
}
|
||||||
|
}.also { updateLogs() }
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.no, null).show()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val LOGS_STYLES = "<style> * { font-family: monospace; } </style>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import com.bartlomiejpluta.ttsserver.initializer.ScriptsInitializer
|
|||||||
import com.bartlomiejpluta.ttsserver.service.foreground.ForegroundService
|
import com.bartlomiejpluta.ttsserver.service.foreground.ForegroundService
|
||||||
import com.bartlomiejpluta.ttsserver.service.state.ServiceState
|
import com.bartlomiejpluta.ttsserver.service.state.ServiceState
|
||||||
import com.bartlomiejpluta.ttsserver.ui.help.HelpActivity
|
import com.bartlomiejpluta.ttsserver.ui.help.HelpActivity
|
||||||
|
import com.bartlomiejpluta.ttsserver.ui.log.LogActivity
|
||||||
import com.bartlomiejpluta.ttsserver.ui.preference.component.PreferencesActivity
|
import com.bartlomiejpluta.ttsserver.ui.preference.component.PreferencesActivity
|
||||||
import dagger.android.support.DaggerAppCompatActivity
|
import dagger.android.support.DaggerAppCompatActivity
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -68,6 +69,7 @@ class MainActivity : DaggerAppCompatActivity() {
|
|||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.open_preferences -> startActivity(Intent(this, PreferencesActivity::class.java))
|
R.id.open_preferences -> startActivity(Intent(this, PreferencesActivity::class.java))
|
||||||
R.id.open_help -> startActivity(Intent(this, HelpActivity::class.java))
|
R.id.open_help -> startActivity(Intent(this, HelpActivity::class.java))
|
||||||
|
R.id.open_logs -> startActivity(Intent(this, LogActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
|
|||||||
15
app/src/main/res/layout/activity_log.xml
Normal file
15
app/src/main/res/layout/activity_log.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.log.LogActivity">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/logview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
5
app/src/main/res/menu/menu_logs.xml
Normal file
5
app/src/main/res/menu/menu_logs.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item android:id="@+id/clear_logs" app:showAsAction="never" android:title="@string/menu_clear_logs" />
|
||||||
|
</menu>
|
||||||
@@ -3,4 +3,5 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<item android:id="@+id/open_preferences" app:showAsAction="never" android:title="@string/menu_settings" />
|
<item android:id="@+id/open_preferences" app:showAsAction="never" android:title="@string/menu_settings" />
|
||||||
<item android:id="@+id/open_help" app:showAsAction="never" android:title="@string/menu_help" />
|
<item android:id="@+id/open_help" app:showAsAction="never" android:title="@string/menu_help" />
|
||||||
|
<item android:id="@+id/open_logs" app:showAsAction="never" android:title="@string/menu_server_logs" />
|
||||||
</menu>
|
</menu>
|
||||||
@@ -4,7 +4,10 @@ return {
|
|||||||
consumer = function(request)
|
consumer = function(request)
|
||||||
local filename = string.format("%s.%s", request.path.filename, request.path.ext)
|
local filename = string.format("%s.%s", request.path.filename, request.path.ext)
|
||||||
local file = server.getCachedFile(filename)
|
local file = server.getCachedFile(filename)
|
||||||
local mime = Mime[request.path.ext:upper()]
|
local format = request.path.ext:upper()
|
||||||
|
local mime = Mime[format]
|
||||||
|
|
||||||
|
log.info("Returning the " .. format .. " file from cache...")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mime = mime,
|
mime = mime,
|
||||||
|
|||||||
@@ -18,8 +18,11 @@ function discoverSonosDevices()
|
|||||||
local output = {}
|
local output = {}
|
||||||
local devices = sonos.discover()
|
local devices = sonos.discover()
|
||||||
|
|
||||||
|
log.info("Discovering Sonos devices...")
|
||||||
for _, device in ipairs(devices) do
|
for _, device in ipairs(devices) do
|
||||||
output[device:getZoneGroupState():getName()] = device
|
local name = device:getZoneGroupState():getName()
|
||||||
|
output[name] = device
|
||||||
|
log.info("Discovered '" .. name .. "' as " .. tostring(device))
|
||||||
end
|
end
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ return {
|
|||||||
local format = (request.path.ext or "wav"):upper()
|
local format = (request.path.ext or "wav"):upper()
|
||||||
local audioFormat = AudioFormat[format]
|
local audioFormat = AudioFormat[format]
|
||||||
local mime = Mime[format]
|
local mime = Mime[format]
|
||||||
|
local language = request.query.lang or "en"
|
||||||
|
|
||||||
local file = tts.sayToCache(request.query.phrase, request.query.lang or "en", audioFormat)
|
log.info("Saying to " .. format .. " file (lang: " .. language .. ")...")
|
||||||
|
local file = tts.sayToCache(request.query.phrase, language, audioFormat)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mime = mime,
|
mime = mime,
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ return {
|
|||||||
accepts = Mime.JSON,
|
accepts = Mime.JSON,
|
||||||
consumer = function(request)
|
consumer = function(request)
|
||||||
if(config.silenceMode()) then return end
|
if(config.silenceMode()) then return end
|
||||||
|
|
||||||
local body = json.decode(request.body)
|
local body = json.decode(request.body)
|
||||||
tts.say(body.text, body.language or "en")
|
local language = body.language or "en"
|
||||||
|
|
||||||
|
log.info("Saying (lang: " .. language .. ")...")
|
||||||
|
tts.say(body.text, language)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
local snapshot
|
local snapshot
|
||||||
|
|
||||||
function prepareTTSFile(phrase, language)
|
function prepareTTSFile(phrase, language)
|
||||||
|
log.info("Saying to cache file (lang: " .. language .. ")...")
|
||||||
local file = tts.sayToCache(phrase, language, AudioFormat.MP3)
|
local file = tts.sayToCache(phrase, language, AudioFormat.MP3)
|
||||||
return string.format("%s/cache/%s", server.url, file:getName())
|
return string.format("%s/cache/%s", server.url, file:getName())
|
||||||
end
|
end
|
||||||
|
|
||||||
function updateSnapshotIfFirst(device)
|
function updateSnapshotIfFirst(device)
|
||||||
if(snapshot == nil) then
|
if(snapshot == nil) then
|
||||||
|
log.info("Dumping the Sonos state snapshot...")
|
||||||
snapshot = device:snapshot()
|
snapshot = device:snapshot()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -14,15 +16,18 @@ end
|
|||||||
function announce(device, data, url)
|
function announce(device, data, url)
|
||||||
device:stop()
|
device:stop()
|
||||||
device:setVolume(data.volume)
|
device:setVolume(data.volume)
|
||||||
|
log.info("Announcing on '" .. data.zone .. "' zone...")
|
||||||
device:playUri(url, "")
|
device:playUri(url, "")
|
||||||
while(device:getPlayState():name() ~= "STOPPED") do
|
while(device:getPlayState():name() ~= "STOPPED") do
|
||||||
thread.sleep(500)
|
thread.sleep(500)
|
||||||
end
|
end
|
||||||
|
log.info("Announcement is complete")
|
||||||
end
|
end
|
||||||
|
|
||||||
function restoreSnapshotIfLast(queueLength)
|
function restoreSnapshotIfLast(queueLength)
|
||||||
if(queueLength() == 0) then
|
if(queueLength() == 0) then
|
||||||
if(snapshot ~= nil) then
|
if(snapshot ~= nil) then
|
||||||
|
log.info("Restoring the Sonos state snapshot...")
|
||||||
snapshot:restore()
|
snapshot:restore()
|
||||||
snapshot = nil
|
snapshot = nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -31,9 +31,14 @@
|
|||||||
|
|
||||||
<string name="menu_settings">Settings</string>
|
<string name="menu_settings">Settings</string>
|
||||||
<string name="menu_help">Help</string>
|
<string name="menu_help">Help</string>
|
||||||
|
<string name="menu_server_logs">Server logs</string>
|
||||||
|
<string name="menu_clear_logs">Clear logs</string>
|
||||||
|
|
||||||
<string name="error_invalid_endpoint_script">Skipping %1$s file because of error:\n%2$s</string>
|
<string name="error_invalid_endpoint_script">Skipping %1$s file because of error:\n%2$s</string>
|
||||||
|
|
||||||
|
<string name="dialog_confirmation">Confirmation</string>
|
||||||
|
<string name="dialog_clear_logs_confirmation">Do you want to clear logs?</string>
|
||||||
|
|
||||||
<string name="error">Error</string>
|
<string name="error">Error</string>
|
||||||
<string name="debug">Debug</string>
|
<string name="debug">Debug</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user