commit 03/09/2025
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package codes
|
package codes
|
||||||
|
|
||||||
|
import content.ScheduleDay
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -23,6 +24,7 @@ class Somecodes {
|
|||||||
val datetimeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")
|
val datetimeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")
|
||||||
val dateformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
|
val dateformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
|
||||||
val timeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm:ss")
|
val timeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm:ss")
|
||||||
|
val timeformat2: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm")
|
||||||
const val KB_threshold = 1024.0
|
const val KB_threshold = 1024.0
|
||||||
const val MB_threshold = KB_threshold * 1024.0
|
const val MB_threshold = KB_threshold * 1024.0
|
||||||
const val GB_threshold = MB_threshold * 1024.0
|
const val GB_threshold = MB_threshold * 1024.0
|
||||||
@@ -165,6 +167,52 @@ class Somecodes {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid schedule time in the format "HH:mm".
|
||||||
|
* @param value The string to check.
|
||||||
|
* @return True if the string is a valid schedule time, false otherwise.
|
||||||
|
*/
|
||||||
|
fun ValidScheduleTime(value: String): Boolean{
|
||||||
|
// format HH:mm
|
||||||
|
try {
|
||||||
|
if (ValidString(value)){
|
||||||
|
timeformat2.parse(value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (_ : Exception){
|
||||||
|
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a schedule day by its name.
|
||||||
|
* @param value The name of the schedule day to find.
|
||||||
|
* @return The name of the schedule day if found, null otherwise.
|
||||||
|
*/
|
||||||
|
fun FindScheduleDay(value: String) : String? {
|
||||||
|
val sd = ScheduleDay.entries.find { sd -> sd.name == value }
|
||||||
|
return sd?.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid schedule day or a valid date.
|
||||||
|
* A valid schedule day is either one of the ScheduleDay enum names or a date in the format "dd/MM/yyyy".
|
||||||
|
* @param value The string to check.
|
||||||
|
* @return True if the string is a valid schedule day or date, false otherwise.
|
||||||
|
*/
|
||||||
|
fun ValidScheduleDay(value: String) : Boolean {
|
||||||
|
if (ValidString(value)){
|
||||||
|
// check if value is one of ScheduleDay enum name
|
||||||
|
if (FindScheduleDay(value) != null){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// check if value is in format dd/MM/yyyy
|
||||||
|
return ValidDate(value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
13
src/content/ScheduleDay.kt
Normal file
13
src/content/ScheduleDay.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package content
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
enum class ScheduleDay(val day: String) {
|
||||||
|
Sunday("Sunday"),
|
||||||
|
Monday("Monday"),
|
||||||
|
Tuesday("Tuesday"),
|
||||||
|
Wednesday("Wednesday"),
|
||||||
|
Thursday("Thursday"),
|
||||||
|
Friday("Friday"),
|
||||||
|
Saturday("Saturday"),
|
||||||
|
Everyday("Everyday")
|
||||||
|
}
|
||||||
@@ -1157,7 +1157,7 @@ class MariaDB(
|
|||||||
* Exports the messagebank table to an XLSX workbook.
|
* Exports the messagebank table to an XLSX workbook.
|
||||||
* @return An XSSFWorkbook containing the messagebank data.
|
* @return An XSSFWorkbook containing the messagebank data.
|
||||||
*/
|
*/
|
||||||
fun Export_Messagebank_XLSX(): XSSFWorkbook {
|
fun Export_Messagebank_XLSX(): XSSFWorkbook? {
|
||||||
try {
|
try {
|
||||||
val statement = connection?.createStatement()
|
val statement = connection?.createStatement()
|
||||||
val resultSet = statement?.executeQuery("SELECT * FROM messagebank")
|
val resultSet = statement?.executeQuery("SELECT * FROM messagebank")
|
||||||
@@ -1188,7 +1188,7 @@ class MariaDB(
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.error { "Error exporting Messagebank, Msg: ${e.message}" }
|
Logger.error { "Error exporting Messagebank, Msg: ${e.message}" }
|
||||||
}
|
}
|
||||||
return XSSFWorkbook()
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Import_Messagebank_XLSX(workbook: XSSFWorkbook): Boolean {
|
fun Import_Messagebank_XLSX(workbook: XSSFWorkbook): Boolean {
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
data class ScheduleBank(var index: UInt, var Description: String, var Day: String, var Time: String, var Soundpath: String, var Repeat: UByte, var Enable: Boolean, var BroadcastZones: String, var Language: String)
|
data class ScheduleBank(
|
||||||
|
var index: UInt,
|
||||||
|
var Description: String,
|
||||||
|
var Day: String,
|
||||||
|
var Time: String,
|
||||||
|
var Soundpath: String,
|
||||||
|
var Repeat: UByte,
|
||||||
|
var Enable: Boolean,
|
||||||
|
var BroadcastZones: String,
|
||||||
|
var Language: String)
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ package web
|
|||||||
import codes.Somecodes
|
import codes.Somecodes
|
||||||
import codes.Somecodes.Companion.ValidDate
|
import codes.Somecodes.Companion.ValidDate
|
||||||
import codes.Somecodes.Companion.ValidFile
|
import codes.Somecodes.Companion.ValidFile
|
||||||
|
import codes.Somecodes.Companion.ValidScheduleDay
|
||||||
|
import codes.Somecodes.Companion.ValidScheduleTime
|
||||||
import codes.Somecodes.Companion.ValidString
|
import codes.Somecodes.Companion.ValidString
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import content.Category
|
import content.Category
|
||||||
|
import content.Language
|
||||||
|
import content.VoiceType
|
||||||
import database.MariaDB
|
import database.MariaDB
|
||||||
import database.Soundbank
|
import database.Soundbank
|
||||||
import io.javalin.Javalin
|
import io.javalin.Javalin
|
||||||
@@ -20,12 +24,13 @@ import io.javalin.apibuilder.ApiBuilder.ws
|
|||||||
import io.javalin.http.Context
|
import io.javalin.http.Context
|
||||||
import io.javalin.json.JavalinJackson
|
import io.javalin.json.JavalinJackson
|
||||||
import io.javalin.websocket.WsMessageContext
|
import io.javalin.websocket.WsMessageContext
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val db: MariaDB) {
|
class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val db: MariaDB) {
|
||||||
|
|
||||||
var app : Javalin? = null
|
var app: Javalin? = null
|
||||||
val objectmapper = jacksonObjectMapper()
|
val objectmapper = jacksonObjectMapper()
|
||||||
|
|
||||||
private fun SendReply(context: WsMessageContext, command: String, value: String) {
|
private fun SendReply(context: WsMessageContext, command: String, value: String) {
|
||||||
@@ -38,13 +43,12 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Start() {
|
fun Start() {
|
||||||
app = Javalin.create {
|
app = Javalin.create { config ->
|
||||||
config ->
|
|
||||||
config.useVirtualThreads = true
|
config.useVirtualThreads = true
|
||||||
config.staticFiles.add("/webpage")
|
config.staticFiles.add("/webpage")
|
||||||
config.jsonMapper(JavalinJackson(jacksonObjectMapper()))
|
config.jsonMapper(JavalinJackson(jacksonObjectMapper()))
|
||||||
config.router.apiBuilder {
|
config.router.apiBuilder {
|
||||||
path("/"){
|
path("/") {
|
||||||
get { ctx ->
|
get { ctx ->
|
||||||
// Serve the main page
|
// Serve the main page
|
||||||
ctx.sessionAttribute("user", null) // Clear user session
|
ctx.sessionAttribute("user", null) // Clear user session
|
||||||
@@ -52,8 +56,8 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
path("login.html"){
|
path("login.html") {
|
||||||
post{ it ->
|
post { it ->
|
||||||
// get username and password from form
|
// get username and password from form
|
||||||
val username = it.formParam("username")
|
val username = it.formParam("username")
|
||||||
val password = it.formParam("password")
|
val password = it.formParam("password")
|
||||||
@@ -84,49 +88,78 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
println("WebSocket closed: ${wsCloseContext.session.remoteAddress}")
|
println("WebSocket closed: ${wsCloseContext.session.remoteAddress}")
|
||||||
}
|
}
|
||||||
ws.onMessage { wsMessageContext ->
|
ws.onMessage { wsMessageContext ->
|
||||||
try{
|
try {
|
||||||
val cmd = objectmapper.readValue(wsMessageContext.message(), WebsocketCommand::class.java)
|
val cmd =
|
||||||
|
objectmapper.readValue(wsMessageContext.message(), WebsocketCommand::class.java)
|
||||||
when (cmd.command) {
|
when (cmd.command) {
|
||||||
"getSystemTime" ->{
|
"getSystemTime" -> {
|
||||||
SendReply(wsMessageContext, cmd.command, LocalDateTime.now().format(Somecodes.datetimeformat1))
|
SendReply(
|
||||||
|
wsMessageContext,
|
||||||
|
cmd.command,
|
||||||
|
LocalDateTime.now().format(Somecodes.datetimeformat1)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"getCPUStatus" ->{
|
|
||||||
|
"getCPUStatus" -> {
|
||||||
Somecodes.getCPUUsage { vv ->
|
Somecodes.getCPUUsage { vv ->
|
||||||
SendReply(wsMessageContext, cmd.command, vv )
|
SendReply(wsMessageContext, cmd.command, vv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"getMemoryStatus" ->{
|
|
||||||
|
"getMemoryStatus" -> {
|
||||||
SendReply(wsMessageContext, cmd.command, Somecodes.getMemoryUsage())
|
SendReply(wsMessageContext, cmd.command, Somecodes.getMemoryUsage())
|
||||||
}
|
}
|
||||||
"getDiskStatus" ->{
|
|
||||||
|
"getDiskStatus" -> {
|
||||||
SendReply(wsMessageContext, cmd.command, Somecodes.getDiskUsage())
|
SendReply(wsMessageContext, cmd.command, Somecodes.getDiskUsage())
|
||||||
}
|
}
|
||||||
"getNetworkStatus" ->{
|
|
||||||
|
"getNetworkStatus" -> {
|
||||||
// TODO Get Network status
|
// TODO Get Network status
|
||||||
SendReply(wsMessageContext, cmd.command, "OK")
|
SendReply(wsMessageContext, cmd.command, "OK")
|
||||||
}
|
}
|
||||||
"getSoundBankList" ->{
|
|
||||||
|
"getSoundBankList" -> {
|
||||||
println("getSoundBankList command received")
|
println("getSoundBankList command received")
|
||||||
SendReply(wsMessageContext, cmd.command, MariaDB.ArrayListtoString(db.SoundbankList))
|
SendReply(
|
||||||
|
wsMessageContext,
|
||||||
|
cmd.command,
|
||||||
|
MariaDB.ArrayListtoString(db.SoundbankList)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"getMessageBankList"->{
|
|
||||||
|
"getMessageBankList" -> {
|
||||||
println("getMessageBankList command received")
|
println("getMessageBankList command received")
|
||||||
SendReply(wsMessageContext, cmd.command, MariaDB.ArrayListtoString(db.MessagebankList))
|
SendReply(
|
||||||
|
wsMessageContext,
|
||||||
|
cmd.command,
|
||||||
|
MariaDB.ArrayListtoString(db.MessagebankList)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"getLanguageList"->{
|
|
||||||
|
"getLanguageList" -> {
|
||||||
println("getLanguageList command received")
|
println("getLanguageList command received")
|
||||||
SendReply(wsMessageContext, cmd.command, MariaDB.ArrayListtoString(db.LanguageLinkList))
|
SendReply(
|
||||||
|
wsMessageContext,
|
||||||
|
cmd.command,
|
||||||
|
MariaDB.ArrayListtoString(db.LanguageLinkList)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"getTimerList"->{
|
|
||||||
|
"getTimerList" -> {
|
||||||
println("getTimerList command received")
|
println("getTimerList command received")
|
||||||
SendReply(wsMessageContext, cmd.command, MariaDB.ArrayListtoString(db.SchedulebankList))
|
SendReply(
|
||||||
|
wsMessageContext,
|
||||||
|
cmd.command,
|
||||||
|
MariaDB.ArrayListtoString(db.SchedulebankList)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
SendReply(wsMessageContext, cmd.command, "Unknown command")
|
SendReply(wsMessageContext, cmd.command, "Unknown command")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception){
|
} catch (e: Exception) {
|
||||||
println("Error processing WebSocket message: ${e.message}")
|
println("Error processing WebSocket message: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +172,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
path("soundbank.html") {
|
path("soundbank.html") {
|
||||||
before {CheckUsers(it)}
|
before { CheckUsers(it) }
|
||||||
}
|
}
|
||||||
path("messagebank.html") {
|
path("messagebank.html") {
|
||||||
before { CheckUsers(it) }
|
before { CheckUsers(it) }
|
||||||
@@ -156,76 +189,79 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
path("timer.html") {
|
path("timer.html") {
|
||||||
before { CheckUsers(it) }
|
before { CheckUsers(it) }
|
||||||
}
|
}
|
||||||
path("api"){
|
path("api") {
|
||||||
path("SoundBank"){
|
path("SoundBank") {
|
||||||
get("List"){
|
get("List") {
|
||||||
// get soundbank list
|
// get soundbank list
|
||||||
it.result(MariaDB.ArrayListtoString(db.SoundbankList))
|
it.result(MariaDB.ArrayListtoString(db.SoundbankList))
|
||||||
}
|
}
|
||||||
post("Add"){
|
post("Add") {
|
||||||
try {
|
try {
|
||||||
val addvalue = objectmapper.readValue(it.body(), Soundbank::class.java)
|
val addvalue = objectmapper.readValue(it.body(), Soundbank::class.java)
|
||||||
if (ValidString(addvalue.Description)){
|
if (ValidString(addvalue.Description)) {
|
||||||
if (ValidString(addvalue.TAG)){
|
if (ValidString(addvalue.TAG)) {
|
||||||
if (ValidString(addvalue.Category)){
|
if (ValidString(addvalue.Category)) {
|
||||||
if (ValidString(addvalue.Language)){
|
if (ValidString(addvalue.Language)) {
|
||||||
if (ValidString(addvalue.Path)){
|
if (ValidString(addvalue.Path)) {
|
||||||
// check apakah TAG sudah ada untuk language dan category yang sama
|
// check apakah TAG sudah ada untuk language dan category yang sama
|
||||||
val exists = db.SoundbankList.any { sb ->
|
val exists = db.SoundbankList.any { sb ->
|
||||||
sb.TAG == addvalue.TAG && sb.Language == addvalue.Language && sb.Category == addvalue.Category
|
sb.TAG == addvalue.TAG && sb.Language == addvalue.Language && sb.Category == addvalue.Category
|
||||||
}
|
}
|
||||||
if (!exists){
|
if (!exists) {
|
||||||
if (ValidFile(addvalue.Path)){
|
if (ValidFile(addvalue.Path)) {
|
||||||
if (db.Add_Soundbank(addvalue)){
|
if (db.Add_Soundbank(addvalue)) {
|
||||||
it.result("OK")
|
it.result("OK")
|
||||||
} else it.status(500).result("Failed to add soundbank to database")
|
} else it.status(500)
|
||||||
} else it.status(400).result("Invalid Path, file does not exist")
|
.result("Failed to add soundbank to database")
|
||||||
} else it.status(400).result("TAG=${addvalue.TAG} already exists for the same Language=${addvalue.Language} and Category=${addvalue.Category}")
|
} else it.status(400)
|
||||||
|
.result("Invalid Path, file does not exist")
|
||||||
|
} else it.status(400)
|
||||||
|
.result("TAG=${addvalue.TAG} already exists for the same Language=${addvalue.Language} and Category=${addvalue.Category}")
|
||||||
} else it.status(400).result("Invalid Path")
|
} else it.status(400).result("Invalid Path")
|
||||||
} else it.status(400).result("Invalid Language")
|
} else it.status(400).result("Invalid Language")
|
||||||
} else it.status(400).result("Invalid Category")
|
} else it.status(400).result("Invalid Category")
|
||||||
} else it.status(400).result("Invalid TAG")
|
} else it.status(400).result("Invalid TAG")
|
||||||
} else it.status(400).result("Invalid Description")
|
} else it.status(400).result("Invalid Description")
|
||||||
} catch (_: Exception){
|
} catch (_: Exception) {
|
||||||
it.status(400).result("Invalid request body")
|
it.status(400).result("Invalid request body")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete("List"){
|
delete("List") {
|
||||||
// truncate soundbank table
|
// truncate soundbank table
|
||||||
if (db.Clear_Soundbank()){
|
if (db.Clear_Soundbank()) {
|
||||||
it.result("OK")
|
it.result("OK")
|
||||||
} else {
|
} else {
|
||||||
it.status(500).result("Failed to truncate soundbank table")
|
it.status(500).result("Failed to truncate soundbank table")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete("DeleteByIndex/{index}"){
|
delete("DeleteByIndex/{index}") {
|
||||||
// delete by index
|
// delete by index
|
||||||
val index = it.pathParam("index").toUIntOrNull()
|
val index = it.pathParam("index").toUIntOrNull()
|
||||||
if (index == null){
|
if (index == null) {
|
||||||
it.status(400).result("Invalid index")
|
it.status(400).result("Invalid index")
|
||||||
} else{
|
} else {
|
||||||
if (db.Delete_Soundbank_by_index(index)){
|
if (db.Delete_Soundbank_by_index(index)) {
|
||||||
it.result("OK")
|
it.result("OK")
|
||||||
} else {
|
} else {
|
||||||
it.status(500).result("Failed to delete soundbank with index $index")
|
it.status(500).result("Failed to delete soundbank with index $index")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
patch("UpdateByIndex/{index}"){
|
patch("UpdateByIndex/{index}") {
|
||||||
// update by index
|
// update by index
|
||||||
val index = it.pathParam("index").toUIntOrNull()
|
val index = it.pathParam("index").toUIntOrNull()
|
||||||
if (index == null){
|
if (index == null) {
|
||||||
// tidak ada path param index
|
// tidak ada path param index
|
||||||
it.status(400).result("Invalid index")
|
it.status(400).result("Invalid index")
|
||||||
} else {
|
} else {
|
||||||
val sb = db.SoundbankList.find{ xx -> xx.index == index }
|
val sb = db.SoundbankList.find { xx -> xx.index == index }
|
||||||
if (sb == null){
|
if (sb == null) {
|
||||||
// soundbank dengan index tersebut tidak ditemukan
|
// soundbank dengan index tersebut tidak ditemukan
|
||||||
it.status(404).result("Soundbank with index $index not found")
|
it.status(404).result("Soundbank with index $index not found")
|
||||||
} else {
|
} else {
|
||||||
// soundbank dengan index tersebut ditemukan, sekarang update
|
// soundbank dengan index tersebut ditemukan, sekarang update
|
||||||
val json : JsonNode = objectmapper.readTree(it.body())
|
val json: JsonNode = objectmapper.readTree(it.body())
|
||||||
if (json.isEmpty){
|
if (json.isEmpty) {
|
||||||
it.status(400).result("UpdateByIndex with index=$index has empty body")
|
it.status(400).result("UpdateByIndex with index=$index has empty body")
|
||||||
} else {
|
} else {
|
||||||
val _description = json.get("Description").asText()
|
val _description = json.get("Description").asText()
|
||||||
@@ -234,17 +270,17 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
val _language = json.get("Language").asText()
|
val _language = json.get("Language").asText()
|
||||||
val _path = json.get("Path").asText()
|
val _path = json.get("Path").asText()
|
||||||
var changed = false
|
var changed = false
|
||||||
if (ValidString(_description) && _description!=sb.Description){
|
if (ValidString(_description) && _description != sb.Description) {
|
||||||
sb.Description = _description
|
sb.Description = _description
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
if (ValidString(_tag) && _tag!=sb.TAG){
|
if (ValidString(_tag) && _tag != sb.TAG) {
|
||||||
sb.TAG = _tag
|
sb.TAG = _tag
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
if (ValidString(_category) && _category!=sb.Category){
|
if (ValidString(_category) && _category != sb.Category) {
|
||||||
if (Category.entries.any {
|
if (Category.entries.any { cat ->
|
||||||
cat -> cat.name == _category
|
cat.name == _category
|
||||||
}) {
|
}) {
|
||||||
sb.Category = _category
|
sb.Category = _category
|
||||||
changed = true
|
changed = true
|
||||||
@@ -253,12 +289,12 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
return@patch
|
return@patch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ValidString(_language) && _language!=sb.Language){
|
if (ValidString(_language) && _language != sb.Language) {
|
||||||
sb.Language = _language
|
sb.Language = _language
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
if (ValidString(_path) && _path!=sb.Path){
|
if (ValidString(_path) && _path != sb.Path) {
|
||||||
if (ValidFile(_path)){
|
if (ValidFile(_path)) {
|
||||||
sb.Path = _path
|
sb.Path = _path
|
||||||
changed = true
|
changed = true
|
||||||
} else {
|
} else {
|
||||||
@@ -266,119 +302,417 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
return@patch
|
return@patch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (changed){
|
if (changed) {
|
||||||
if (db.Update_Soundbank_by_index(index, sb)){
|
if (db.Update_Soundbank_by_index(index, sb)) {
|
||||||
it.result("OK")
|
it.result("OK")
|
||||||
} else it.status(500).result("Failed to update soundbank with index $index")
|
} else it.status(500).result("Failed to update soundbank with index $index")
|
||||||
} else it.status(400).result("Nothing has changed for soundbank with index $index")
|
} else it.status(400)
|
||||||
|
.result("Nothing has changed for soundbank with index $index")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
get("ExportXLSX"){
|
get("ExportXLSX") {
|
||||||
|
val xlsxdata = db.Export_Soundbank_XLSX()
|
||||||
|
if (xlsxdata != null) {
|
||||||
|
it.header(
|
||||||
|
"Content-Type",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
)
|
||||||
|
it.header("Content-Disposition", "attachment; filename=\"soundbank.xlsx\"")
|
||||||
|
it.outputStream().use { out ->
|
||||||
|
xlsxdata.write(out)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
it.status(500).result("Failed to export soundbank to XLSX")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
post("ImportXLSX"){
|
post("ImportXLSX") {
|
||||||
val uploaded = it.uploadedFile("file")
|
val uploaded = it.uploadedFile("file")
|
||||||
if (uploaded==null){
|
if (uploaded == null) {
|
||||||
it.status(400).result("No file uploaded")
|
it.status(400).result("No file uploaded")
|
||||||
return@post
|
return@post
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
val xlsx = XSSFWorkbook(uploaded.content())
|
||||||
|
if (db.Import_Soundbank_XLSX(xlsx)) {
|
||||||
|
it.result("OK")
|
||||||
|
} else {
|
||||||
|
it.status(500).result("Failed to import soundbank from XLSX")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
it.status(400).result("Invalid XLSX file")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path("MessageBank"){
|
path("MessageBank") {
|
||||||
get("List"){
|
get("List") {
|
||||||
// get messagebank list
|
// get messagebank list
|
||||||
it.result(MariaDB.ArrayListtoString(db.MessagebankList))
|
it.result(MariaDB.ArrayListtoString(db.MessagebankList))
|
||||||
}
|
}
|
||||||
delete("List"){
|
delete("List") {
|
||||||
// truncate messagebank table
|
// truncate messagebank table
|
||||||
if (db.Clear_Messagebank()){
|
if (db.Clear_Messagebank()) {
|
||||||
it.result("OK")
|
it.result("OK")
|
||||||
} else {
|
} else {
|
||||||
it.status(500).result("Failed to truncate messagebank table")
|
it.status(500).result("Failed to truncate messagebank table")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete("DeleteByIndex/{index}"){
|
delete("DeleteByIndex/{index}") {
|
||||||
// delete by index
|
// delete by index
|
||||||
val index = it.pathParam("index").toUIntOrNull()
|
val index = it.pathParam("index").toUIntOrNull()
|
||||||
if (index == null){
|
if (index == null) {
|
||||||
it.status(400).result("Invalid index")
|
it.status(400).result("Invalid index")
|
||||||
} else{
|
} else {
|
||||||
if (db.Delete_Messagebank_by_index(index)){
|
if (db.Delete_Messagebank_by_index(index)) {
|
||||||
it.result("OK")
|
it.result("OK")
|
||||||
} else {
|
} else {
|
||||||
it.status(500).result("Failed to delete messagebank with index $index")
|
it.status(500).result("Failed to delete messagebank with index $index")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
patch("UpdateByIndex/{index}") {
|
||||||
|
// update messagebank by index
|
||||||
|
val index = it.pathParam("index").toUIntOrNull()
|
||||||
|
if (index == null) {
|
||||||
|
it.status(400).result("Invalid index")
|
||||||
|
} else {
|
||||||
|
val mb = db.MessagebankList.find { xx -> xx.index == index }
|
||||||
|
if (mb == null) {
|
||||||
|
it.status(404).result("Messagebank with index $index not found")
|
||||||
|
} else {
|
||||||
|
val json: JsonNode = objectmapper.readTree(it.body())
|
||||||
|
if (json.isEmpty) {
|
||||||
|
it.status(400).result("UpdateByIndex with index=$index has empty body")
|
||||||
|
} else {
|
||||||
|
val _description = json.get("Description").asText()
|
||||||
|
val _language = json.get("Language").asText()
|
||||||
|
val _ann_id = json.get("ANN_ID").asInt().toUInt()
|
||||||
|
val _voice_type = json.get("Voice_Type").asText()
|
||||||
|
val _message_detail = json.get("Message_Detail").asText()
|
||||||
|
val _message_tags = json.get("Message_TAGS").asText()
|
||||||
|
|
||||||
|
var changed = false
|
||||||
|
if (ValidString(_description) && _description != mb.Description) {
|
||||||
|
mb.Description = _description
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (ValidString(_language) && _language != mb.Language) {
|
||||||
|
mb.Language = _language
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (_ann_id > 0u && _ann_id != mb.ANN_ID) {
|
||||||
|
mb.ANN_ID = _ann_id
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (ValidString(_voice_type) && _voice_type != mb.Voice_Type) {
|
||||||
|
if (VoiceType.entries.any { vt -> vt.name == _voice_type }) {
|
||||||
|
mb.Voice_Type = _voice_type
|
||||||
|
changed = true
|
||||||
|
} else {
|
||||||
|
it.status(400).result("Invalid Voice_Type")
|
||||||
|
return@patch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ValidString(_message_detail) && _message_detail != mb.Message_Detail) {
|
||||||
|
mb.Message_Detail = _message_detail
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (ValidString(_message_tags) && _message_tags != mb.Message_TAGS) {
|
||||||
|
mb.Message_TAGS = _message_tags
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
if (db.Update_Messagebank_by_index(index, mb)) {
|
||||||
|
it.result("OK")
|
||||||
|
} else it.status(500)
|
||||||
|
.result("Failed to update messagebank with index $index")
|
||||||
|
} else it.status(400)
|
||||||
|
.result("Nothing has changed for messagebank with index $index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get("ExportXLSX") {
|
||||||
|
val xlsxdata = db.Export_Messagebank_XLSX()
|
||||||
|
if (xlsxdata != null) {
|
||||||
|
it.header(
|
||||||
|
"Content-Type",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
)
|
||||||
|
it.header("Content-Disposition", "attachment; filename=\"messagebank.xlsx\"")
|
||||||
|
it.outputStream().use { out ->
|
||||||
|
xlsxdata.write(out)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
it.status(500).result("Failed to export messagebank to XLSX")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post("ImportXLSX") {
|
||||||
|
val uploaded = it.uploadedFile("file")
|
||||||
|
if (uploaded == null) {
|
||||||
|
it.status(400).result("No file uploaded")
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val xlsx = XSSFWorkbook(uploaded.content())
|
||||||
|
if (db.Import_Messagebank_XLSX(xlsx)) {
|
||||||
|
it.result("OK")
|
||||||
|
} else {
|
||||||
|
it.status(500).result("Failed to import messagebank from XLSX")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
it.status(400).result("Invalid XLSX file")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
path("LanguageLink"){
|
path("LanguageLink") {
|
||||||
get("List"){
|
get("List") {
|
||||||
// get language link list
|
// get language link list
|
||||||
it.result(MariaDB.ArrayListtoString(db.LanguageLinkList))
|
it.result(MariaDB.ArrayListtoString(db.LanguageLinkList))
|
||||||
}
|
}
|
||||||
delete("List"){
|
delete("List") {
|
||||||
// truncate language link table
|
// truncate language link table
|
||||||
if (db.Clear_LanguageLink()){
|
if (db.Clear_LanguageLink()) {
|
||||||
it.result("OK")
|
it.result("OK")
|
||||||
} else {
|
} else {
|
||||||
it.status(500).result("Failed to truncate language link table")
|
it.status(500).result("Failed to truncate language link table")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete("DeleteByIndex/{index}"){
|
delete("DeleteByIndex/{index}") {
|
||||||
// delete by index
|
// delete by index
|
||||||
val index = it.pathParam("index").toUIntOrNull()
|
val index = it.pathParam("index").toUIntOrNull()
|
||||||
if (index == null){
|
if (index == null) {
|
||||||
it.status(400).result("Invalid index")
|
it.status(400).result("Invalid index")
|
||||||
} else{
|
} else {
|
||||||
if (db.Delete_LanguageLink_by_index(index)){
|
if (db.Delete_LanguageLink_by_index(index)) {
|
||||||
it.result("OK")
|
it.result("OK")
|
||||||
} else {
|
} else {
|
||||||
it.status(500).result("Failed to delete language link with index $index")
|
it.status(500).result("Failed to delete language link with index $index")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
patch("UpdateByIndex/{index}") {
|
||||||
|
// update by index
|
||||||
|
val index = it.pathParam("index").toUIntOrNull()
|
||||||
|
if (index == null) {
|
||||||
|
it.status(400).result("Invalid index")
|
||||||
|
} else {
|
||||||
|
val ll = db.LanguageLinkList.find { xx -> xx.index == index }
|
||||||
|
if (ll == null) {
|
||||||
|
it.status(404).result("Language link with index $index not found")
|
||||||
|
} else {
|
||||||
|
val json: JsonNode = objectmapper.readTree(it.body())
|
||||||
|
if (json.isEmpty) {
|
||||||
|
it.status(400).result("UpdateByIndex with index=$index has empty body")
|
||||||
|
} else {
|
||||||
|
val _tag = json.get("TAG").asText()
|
||||||
|
val _language = json.get("Language").asText()
|
||||||
|
var changed = false
|
||||||
|
if (ValidString(_language) && _language != ll.Language) {
|
||||||
|
ll.Language = _language
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (ValidString(_tag) && _tag != ll.TAG) {
|
||||||
|
ll.TAG = _tag
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
if (db.Update_LanguageLink_by_index(index, ll)) {
|
||||||
|
it.result("OK")
|
||||||
|
} else it.status(500)
|
||||||
|
.result("Failed to update language link with index $index")
|
||||||
|
} else it.status(400)
|
||||||
|
.result("Nothing has changed for language link with index $index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get("ExportXLSX") {
|
||||||
|
val xlsxdata = db.Export_LanguageLink_XLSX()
|
||||||
|
if (xlsxdata != null) {
|
||||||
|
it.header(
|
||||||
|
"Content-Type",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
)
|
||||||
|
it.header("Content-Disposition", "attachment; filename=\"languagelink.xlsx\"")
|
||||||
|
it.outputStream().use { out ->
|
||||||
|
xlsxdata.write(out)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
it.status(500).result("Failed to export language link to XLSX")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post("ImportXLSX") {
|
||||||
|
val uploaded = it.uploadedFile("file")
|
||||||
|
if (uploaded == null) {
|
||||||
|
it.status(400).result("No file uploaded")
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val xlsx = XSSFWorkbook(uploaded.content())
|
||||||
|
if (db.Import_LanguageLink_XLSX(xlsx)) {
|
||||||
|
it.result("OK")
|
||||||
|
} else {
|
||||||
|
it.status(500).result("Failed to import language link from XLSX")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
it.status(400).result("Invalid XLSX file")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
path("ScheduleBank"){
|
path("ScheduleBank") {
|
||||||
get("List"){
|
get("List") {
|
||||||
// get timer list
|
// get timer list
|
||||||
it.result(MariaDB.ArrayListtoString(db.SchedulebankList))
|
it.result(MariaDB.ArrayListtoString(db.SchedulebankList))
|
||||||
}
|
}
|
||||||
delete("List"){
|
delete("List") {
|
||||||
// truncate timer table
|
// truncate timer table
|
||||||
if (db.Clear_Schedulebank()){
|
if (db.Clear_Schedulebank()) {
|
||||||
it.result("OK")
|
it.result("OK")
|
||||||
} else {
|
} else {
|
||||||
it.status(500).result("Failed to truncate schedulebank table")
|
it.status(500).result("Failed to truncate schedulebank table")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete("DeleteByIndex/{index}"){
|
delete("DeleteByIndex/{index}") {
|
||||||
// delete by index
|
// delete by index
|
||||||
val index = it.pathParam("index").toUIntOrNull()
|
val index = it.pathParam("index").toUIntOrNull()
|
||||||
if (index == null){
|
if (index == null) {
|
||||||
it.status(400).result("Invalid index")
|
it.status(400).result("Invalid index")
|
||||||
} else{
|
} else {
|
||||||
if (db.Delete_Schedulebank_by_index(index)){
|
if (db.Delete_Schedulebank_by_index(index)) {
|
||||||
it.result("OK")
|
it.result("OK")
|
||||||
} else {
|
} else {
|
||||||
it.status(500).result("Failed to delete schedule with index $index")
|
it.status(500).result("Failed to delete schedule with index $index")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
patch("UpdateByIndex/{index}") {
|
||||||
|
// update by index
|
||||||
|
val index = it.pathParam("index").toUIntOrNull()
|
||||||
|
if (index == null) {
|
||||||
|
it.status(400).result("Invalid index")
|
||||||
|
} else {
|
||||||
|
val sb = db.SchedulebankList.find { xx -> xx.index == index }
|
||||||
|
if (sb == null) {
|
||||||
|
it.status(404).result("Schedule with index $index not found")
|
||||||
|
} else {
|
||||||
|
val json: JsonNode = objectmapper.readTree(it.body())
|
||||||
|
if (json.isEmpty) {
|
||||||
|
it.status(400).result("UpdateByIndex with index=$index has empty body")
|
||||||
|
} else {
|
||||||
|
val _description = json.get("Description").asText()
|
||||||
|
val _time = json.get("Time").asText()
|
||||||
|
val _day = json.get("Day").asText()
|
||||||
|
val _soundpath = json.get("Soundpath").asText()
|
||||||
|
val _repeat = json.get("Repeat").asInt().toUByte()
|
||||||
|
val _enable = json.get("Enable").asBoolean()
|
||||||
|
val _broadcast_zones = json.get("BroadcastZones").asText()
|
||||||
|
val _language = json.get("Language").asText()
|
||||||
|
var changed = false
|
||||||
|
if (ValidString(_description) && _description != sb.Description) {
|
||||||
|
sb.Description = _description
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (ValidString(_time) && _time != sb.Time) {
|
||||||
|
if (ValidScheduleTime(_time)) {
|
||||||
|
sb.Time = _time
|
||||||
|
changed = true
|
||||||
|
} else {
|
||||||
|
it.status(400).result("Invalid Time format, must be HH:mm")
|
||||||
|
return@patch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ValidString(_day) && _day != sb.Day) {
|
||||||
|
if (ValidScheduleDay(_day)) {
|
||||||
|
sb.Day = _day
|
||||||
|
changed = true
|
||||||
|
} else {
|
||||||
|
it.status(400).result("Invalid Day format")
|
||||||
|
return@patch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ValidString(_soundpath) && _soundpath != sb.Soundpath) {
|
||||||
|
sb.Soundpath = _soundpath
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (_repeat != sb.Repeat) {
|
||||||
|
sb.Repeat = _repeat
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (_enable != sb.Enable) {
|
||||||
|
sb.Enable = _enable
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (ValidString(_broadcast_zones) && _broadcast_zones != sb.BroadcastZones) {
|
||||||
|
sb.BroadcastZones = _broadcast_zones
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (ValidString(_language) && _language != sb.Language) {
|
||||||
|
if (Language.entries.any{ lang -> lang.name == _language }) {
|
||||||
|
sb.Language = _language
|
||||||
|
changed = true
|
||||||
|
} else {
|
||||||
|
it.status(400).result("Invalid Language")
|
||||||
|
return@patch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
if (db.Update_Schedulebank_by_index(index, sb)) {
|
||||||
|
it.result("OK")
|
||||||
|
} else it.status(500)
|
||||||
|
.result("Failed to update schedule with index $index")
|
||||||
|
} else it.status(400)
|
||||||
|
.result("Nothing has changed for schedule with index $index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get("ExportXLSX") {
|
||||||
|
val xlsxdata = db.Export_Schedulebank_XLSX()
|
||||||
|
if (xlsxdata != null) {
|
||||||
|
it.header(
|
||||||
|
"Content-Type",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
)
|
||||||
|
it.header("Content-Disposition", "attachment; filename=\"schedulebank.xlsx\"")
|
||||||
|
it.outputStream().use { out ->
|
||||||
|
xlsxdata.write(out)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
it.status(500).result("Failed to export schedulebank to XLSX")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post("ImportXLSX") {
|
||||||
|
val uploaded = it.uploadedFile("file")
|
||||||
|
if (uploaded == null) {
|
||||||
|
it.status(400).result("No file uploaded")
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val xlsx = XSSFWorkbook(uploaded.content())
|
||||||
|
if (db.Import_Schedulebank_XLSX(xlsx)) {
|
||||||
|
it.result("OK")
|
||||||
|
} else {
|
||||||
|
it.status(500).result("Failed to import schedulebank from XLSX")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
it.status(400).result("Invalid XLSX file")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
path("Log"){
|
path("Log") {
|
||||||
get("List/<logdate>/<logfilter>"){ get1 ->
|
get("List/<logdate>/<logfilter>") { get1 ->
|
||||||
val logdate = get1.pathParam("logdate")
|
val logdate = get1.pathParam("logdate")
|
||||||
val logfilter = get1.pathParam("logfilter")
|
val logfilter = get1.pathParam("logfilter")
|
||||||
if (ValidDate(logdate)){
|
if (ValidDate(logdate)) {
|
||||||
if (ValidString(logfilter)){
|
if (ValidString(logfilter)) {
|
||||||
// ada log filter
|
// ada log filter
|
||||||
db.GetLog(logdate, logfilter){
|
db.GetLog(logdate, logfilter) {
|
||||||
get1.result(MariaDB.ArrayListtoString(it))
|
get1.result(MariaDB.ArrayListtoString(it))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
db.GetLog(logdate){
|
db.GetLog(logdate) {
|
||||||
get1.result(MariaDB.ArrayListtoString(it))
|
get1.result(MariaDB.ArrayListtoString(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,19 +726,18 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun CheckUsers(ctx: Context) {
|
||||||
fun CheckUsers(ctx: Context){
|
|
||||||
val user = ctx.sessionAttribute<String?>("user")
|
val user = ctx.sessionAttribute<String?>("user")
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
ctx.redirect("login.html")
|
ctx.redirect("login.html")
|
||||||
}
|
}
|
||||||
val foundUser = userlist.find { it.first == user }
|
val foundUser = userlist.find { it.first == user }
|
||||||
if (foundUser==null) {
|
if (foundUser == null) {
|
||||||
ctx.redirect("login.html")
|
ctx.redirect("login.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Stop(){
|
fun Stop() {
|
||||||
app?.stop()
|
app?.stop()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user