commit 04/11/2025

This commit is contained in:
2025-11-04 17:02:53 +07:00
parent fc1291dcd5
commit 72c509feec
16 changed files with 18196 additions and 172 deletions

View File

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

View File

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

View 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>()
}

View File

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