commit 04/11/2025
This commit is contained in:
@@ -33,7 +33,7 @@ lateinit var audioPlayer: AudioPlayer
|
||||
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
|
||||
lateinit var udpreceiver: UDPReceiver
|
||||
lateinit var tcpreceiver: TCPReceiver
|
||||
const val version = "0.0.13 (30/10/2025)"
|
||||
const val version = "0.0.14 (04/11/2025)"
|
||||
// AAS 64 channels
|
||||
const val max_channel = 64
|
||||
|
||||
|
||||
@@ -481,6 +481,10 @@ class Somecodes {
|
||||
return Files.exists(value) && Files.isDirectory(value)
|
||||
}
|
||||
|
||||
fun String_To_List(str: String, separator: String = ";") : MutableList<String>{
|
||||
return str.split(separator).map { it.trim() }.filter { it.isNotEmpty() }.toMutableList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid date in the format "dd/MM/yyyy".
|
||||
* @param value The string to check.
|
||||
@@ -647,6 +651,50 @@ class Somecodes {
|
||||
String.format("%02d:%02d:%02d", hours, minutes, seconds)
|
||||
} else ""
|
||||
}
|
||||
|
||||
fun Generate_WAV_Header() : ByteArray {
|
||||
val sampleRate = 44100
|
||||
val bitsPerSample = 16
|
||||
val channels = 1
|
||||
val byteRate = sampleRate * channels * (bitsPerSample / 8)
|
||||
val blockAlign = channels * (bitsPerSample / 8)
|
||||
val subchunk2Size = 0 // unknown / placeholder
|
||||
val chunkSize = 36 + subchunk2Size
|
||||
|
||||
val header = ByteArray(44)
|
||||
|
||||
fun writeIntLE(offset: Int, value: Int) {
|
||||
header[offset] = (value and 0xFF).toByte()
|
||||
header[offset + 1] = ((value shr 8) and 0xFF).toByte()
|
||||
header[offset + 2] = ((value shr 16) and 0xFF).toByte()
|
||||
header[offset + 3] = ((value shr 24) and 0xFF).toByte()
|
||||
}
|
||||
fun writeShortLE(offset: Int, value: Int) {
|
||||
header[offset] = (value and 0xFF).toByte()
|
||||
header[offset + 1] = ((value shr 8) and 0xFF).toByte()
|
||||
}
|
||||
|
||||
// RIFF chunk descriptor
|
||||
System.arraycopy("RIFF".toByteArray(), 0, header, 0, 4)
|
||||
writeIntLE(4, chunkSize)
|
||||
System.arraycopy("WAVE".toByteArray(), 0, header, 8, 4)
|
||||
|
||||
// fmt subchunk
|
||||
System.arraycopy("fmt ".toByteArray(), 0, header, 12, 4)
|
||||
writeIntLE(16, 16) // Subchunk1Size for PCM
|
||||
writeShortLE(20, 1) // AudioFormat = 1 (PCM)
|
||||
writeShortLE(22, channels)
|
||||
writeIntLE(24, sampleRate)
|
||||
writeIntLE(28, byteRate)
|
||||
writeShortLE(32, blockAlign)
|
||||
writeShortLE(34, bitsPerSample)
|
||||
|
||||
// data subchunk
|
||||
System.arraycopy("data".toByteArray(), 0, header, 36, 4)
|
||||
writeIntLE(40, subchunk2Size)
|
||||
|
||||
return header
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
29
src/web/SemiAutoInitData.kt
Normal file
29
src/web/SemiAutoInitData.kt
Normal file
@@ -0,0 +1,29 @@
|
||||
package web
|
||||
|
||||
class SemiAutoInitData(val username: String) {
|
||||
// equal to "MSG" in android command
|
||||
var messages = mutableListOf<String>()
|
||||
// equal to "VARAP" in android command
|
||||
var airlines = mutableListOf<String>()
|
||||
// equal to "VARCITY" in android command
|
||||
var cities = mutableListOf<String>()
|
||||
// equal to "VARPLACES" in android command
|
||||
var places = mutableListOf<String>()
|
||||
// equal to "VARSHALAT" in android command
|
||||
var shalat = mutableListOf<String>()
|
||||
// equal to "VARSEQUENCE" in android command
|
||||
var sequences = mutableListOf<String>()
|
||||
// equal to "VARREASON" in android command
|
||||
var reasons = mutableListOf<String>()
|
||||
// equal to "VARPROCEDURE" in android command
|
||||
var procedures = mutableListOf<String>()
|
||||
// equal to "VARGATE" in android command
|
||||
var gates = mutableListOf<String>()
|
||||
// equal to "VARCOMPENSATION" in android command
|
||||
var compensation = mutableListOf<String>()
|
||||
// equal to "VARGREETING" in android command
|
||||
var greetings = mutableListOf<String>()
|
||||
// equal to "ZONE" in android command
|
||||
var broadcastzones = mutableListOf<String>()
|
||||
|
||||
}
|
||||
@@ -3,10 +3,13 @@ package web
|
||||
import StreamerOutputs
|
||||
import barix.BarixConnection
|
||||
import codes.Somecodes
|
||||
import codes.Somecodes.Companion.Generate_WAV_Header
|
||||
import codes.Somecodes.Companion.GetSensorsInfo
|
||||
import codes.Somecodes.Companion.GetUptime
|
||||
import codes.Somecodes.Companion.ListAudioFiles
|
||||
import codes.Somecodes.Companion.String_To_List
|
||||
import codes.Somecodes.Companion.ValiDateForLogHtml
|
||||
import codes.Somecodes.Companion.ValidDate
|
||||
import codes.Somecodes.Companion.ValidDirectory
|
||||
import codes.Somecodes.Companion.ValidFile
|
||||
import codes.Somecodes.Companion.ValidIPV4
|
||||
@@ -14,6 +17,7 @@ import codes.Somecodes.Companion.ValidScheduleDay
|
||||
import codes.Somecodes.Companion.ValidScheduleTime
|
||||
import codes.Somecodes.Companion.ValidString
|
||||
import codes.Somecodes.Companion.ValidStrings
|
||||
import codes.Somecodes.Companion.datetimeformat1
|
||||
import codes.configFile
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
@@ -31,13 +35,7 @@ import database.Soundbank
|
||||
import database.UserDB
|
||||
import db
|
||||
import io.javalin.Javalin
|
||||
import io.javalin.apibuilder.ApiBuilder.before
|
||||
import io.javalin.apibuilder.ApiBuilder.delete
|
||||
import io.javalin.apibuilder.ApiBuilder.get
|
||||
import io.javalin.apibuilder.ApiBuilder.patch
|
||||
import io.javalin.apibuilder.ApiBuilder.path
|
||||
import io.javalin.apibuilder.ApiBuilder.post
|
||||
import io.javalin.apibuilder.ApiBuilder.ws
|
||||
import io.javalin.apibuilder.ApiBuilder.*
|
||||
import io.javalin.http.Context
|
||||
import io.javalin.json.JavalinJackson
|
||||
import io.javalin.websocket.WsMessageContext
|
||||
@@ -45,6 +43,7 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import java.nio.file.Files
|
||||
import java.time.LocalDateTime
|
||||
import codes.configKeys
|
||||
import database.QueueTable
|
||||
import org.tinylog.Logger
|
||||
import java.io.File
|
||||
import java.io.PipedInputStream
|
||||
@@ -69,6 +68,12 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
}
|
||||
|
||||
fun Start() {
|
||||
Start_WebServer()
|
||||
Start_SemiAutoServer()
|
||||
|
||||
}
|
||||
|
||||
private fun Start_WebServer(){
|
||||
Logger.info { "Starting Web Application on port $listenPort" }
|
||||
app = Javalin.create { config ->
|
||||
config.useVirtualThreads = true
|
||||
@@ -127,7 +132,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
objectmapper.readValue(wsMessageContext.message(), WebsocketCommand::class.java)
|
||||
when (cmd.command) {
|
||||
"getSystemTime" -> {
|
||||
val systemtime = LocalDateTime.now().format(Somecodes.datetimeformat1)
|
||||
val systemtime = LocalDateTime.now().format(datetimeformat1)
|
||||
val uptime = GetUptime()
|
||||
SendReply(
|
||||
wsMessageContext,
|
||||
@@ -251,6 +256,8 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
val key = ctx.req().remoteAddr + ":" + ctx.req().remotePort
|
||||
val pipeIN = PipedInputStream(8192)
|
||||
val pipeOUT = PipedOutputStream(pipeIN)
|
||||
// write WAV Header 44 bytes contains samplingrate 44100 Hz, 16 bit, mono
|
||||
pipeOUT.write(Generate_WAV_Header())
|
||||
bc.AddPipeOut(key, pipeOUT)
|
||||
ctx.contentType("audio/wav")
|
||||
ctx.header("Cache-Control", "no-cache")
|
||||
@@ -2034,6 +2041,10 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
}
|
||||
}
|
||||
}.start(listenPort)
|
||||
}
|
||||
|
||||
private fun Start_SemiAutoServer(){
|
||||
Logger.info {"Starting SemiAuto web server at port ${listenPort+1}"}
|
||||
semiauto = Javalin.create{ config ->
|
||||
config.useVirtualThreads = true
|
||||
config.staticFiles.add ("/semiauto")
|
||||
@@ -2070,11 +2081,129 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
path("log.html"){
|
||||
before { CheckSemiAutoUsers(it) }
|
||||
}
|
||||
path("api"){
|
||||
path("Initialize"){
|
||||
get { ctx ->
|
||||
val username = ctx.cookie("semiauto-user") ?: ""
|
||||
if (username.isNotEmpty()){
|
||||
val user = db.userDB.List.find { u -> u.username==username }
|
||||
if (user!=null){
|
||||
val result = SemiAutoInitData(username)
|
||||
// messages
|
||||
String_To_List(user.messagebank_ann_id).forEach { msg ->
|
||||
db.messageDB.List.filter{it.ANN_ID==msg.toUInt()}.forEach {xx ->
|
||||
result.messages.add("${xx.ANN_ID};${xx.Description};${xx.Language};${xx.Message_Detail}")
|
||||
}
|
||||
}
|
||||
// broacdcast zones
|
||||
result.broadcastzones = String_To_List(user.broadcastzones)
|
||||
// cities
|
||||
String_To_List(user.city_tags).forEach { ct ->
|
||||
db.soundDB.List.firstOrNull { it.TAG == ct && it.Category == Category.City.name }
|
||||
?.let{
|
||||
result.cities.add("${it.TAG};${it.Description}")
|
||||
}
|
||||
}
|
||||
// airplane names
|
||||
String_To_List(user.airline_tags).forEach { at ->
|
||||
db.soundDB.List.firstOrNull { it.TAG == at && it.Category == Category.Airplane_Name.name }
|
||||
?.let{
|
||||
result.airlines.add("${it.TAG};${it.Description}")
|
||||
}
|
||||
}
|
||||
// places
|
||||
db.soundDB.List.filter{it.Category==Category.Places.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.places.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// shalat
|
||||
db.soundDB.List.filter{it.Category==Category.Shalat.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.shalat.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// sequences
|
||||
db.soundDB.List.filter{it.Category==Category.Sequence.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.sequences.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// reasons
|
||||
db.soundDB.List.filter{it.Category==Category.Reason.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.reasons.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// procedures
|
||||
db.soundDB.List.filter{it.Category==Category.Procedure.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.procedures.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// gates
|
||||
db.soundDB.List.filter{it.Category==Category.Gate.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.gates.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// compensation
|
||||
db.soundDB.List.filter{it.Category==Category.Compensation.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.compensation.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// greetings
|
||||
db.soundDB.List.filter{it.Category==Category.Greeting.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.greetings.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
|
||||
ctx.result(objectmapper.writeValueAsString(result))
|
||||
} else ResultMessageString(ctx, 400, "User not found")
|
||||
} else ResultMessageString(ctx, 400, "Username is empty")
|
||||
}
|
||||
}
|
||||
path("SemiAuto"){
|
||||
post { ctx ->
|
||||
val json : JsonNode = objectmapper.readTree(ctx.body())
|
||||
// butuh description, languages, tags, dan broadcastzones
|
||||
val description = json.get("description").asText("")
|
||||
val languages = json.get("languages").asText("")
|
||||
val tags = json.get("tags").asText("")
|
||||
val broadcastzones = json.get("broadcastzones").asText("")
|
||||
if (description.isNotEmpty()){
|
||||
if (languages.isNotEmpty()){
|
||||
if (tags.isNotEmpty()){
|
||||
if (broadcastzones.isNotEmpty()){
|
||||
val qt = QueueTable(
|
||||
0u,
|
||||
LocalDateTime.now().format(datetimeformat1),
|
||||
"SEMIAUTOWEB",
|
||||
"SOUNDBANK",
|
||||
description,
|
||||
tags,
|
||||
broadcastzones,
|
||||
1u,
|
||||
languages
|
||||
)
|
||||
if (db.queuetableDB.Add(qt)){
|
||||
db.queuetableDB.Resort()
|
||||
Logger.info{"SemiAutoWeb added to queue table: $qt" }
|
||||
println("SemiAuto added to queue table: ${objectmapper.writeValueAsString(qt)}")
|
||||
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
|
||||
} else ResultMessageString(ctx, 500, "Failed to add to queue table")
|
||||
} else ResultMessageString(ctx, 400, "Broadcast zones cannot be empty")
|
||||
} else ResultMessageString(ctx, 400, "Tags cannot be empty")
|
||||
} else ResultMessageString(ctx, 400, "Languages cannot be empty")
|
||||
} else ResultMessageString(ctx, 400, "Description cannot be empty")
|
||||
|
||||
}
|
||||
}
|
||||
path("Log"){
|
||||
get("/{datelog}"){ ctx ->
|
||||
val datelog = ctx.pathParam("datelog")
|
||||
if (ValidDate(datelog)){
|
||||
println("SemiAuto Get Log for date $datelog")
|
||||
db.GetLogForHtml(datelog){
|
||||
loghtml ->
|
||||
val resultstring = objectmapper.writeValueAsString(loghtml)
|
||||
println("Log HTML for date $datelog: $resultstring")
|
||||
ctx.result(resultstring)
|
||||
}
|
||||
|
||||
} else ResultMessageString(ctx, 400, "Invalid date format")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start(listenPort+1)
|
||||
|
||||
}
|
||||
|
||||
fun ResultMessageString(ctx: Context, code: Int, message: String) {
|
||||
ctx.status(code).result(objectmapper.writeValueAsString(resultMessage(message)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user