From efe243e44086b0d965e6d1c6e58ebc617a89e4bd Mon Sep 17 00:00:00 2001 From: rdkartono Date: Wed, 8 Oct 2025 14:10:50 +0700 Subject: [PATCH] commit 07/10/2025 --- src/Main.kt | 15 + src/MainExtension01.kt | 19 +- src/audio/AudioPlayer.kt | 56 +-- src/audio/TCPReceiver.kt | 104 ++++ src/commandServer/PagingJob.kt | 6 +- .../TCP_Android_Command_Server.kt | 447 +++++++++++++----- src/content/Category.kt | 5 +- src/database/MariaDB.kt | 6 +- 8 files changed, 497 insertions(+), 161 deletions(-) create mode 100644 src/audio/TCPReceiver.kt diff --git a/src/Main.kt b/src/Main.kt index 42e6596..ce2fd70 100644 --- a/src/Main.kt +++ b/src/Main.kt @@ -1,5 +1,6 @@ import audio.AudioPlayer import audio.ContentCache +import audio.TCPReceiver import audio.UDPReceiver import barix.BarixConnection import barix.TCP_Barix_Command_Server @@ -27,6 +28,7 @@ lateinit var db: MariaDB lateinit var audioPlayer: AudioPlayer val StreamerOutputs: MutableMap = HashMap() lateinit var udpreceiver: UDPReceiver +lateinit var tcpreceiver: TCPReceiver const val version = "0.0.2 (23/09/2025)" // AAS 64 channels const val max_channel = 64 @@ -140,6 +142,15 @@ fun main() { Logger.error { "Failed to start UDP Receiver on port 5002" } } + tcpreceiver = TCPReceiver() + if (tcpreceiver.Start()) { + Logger.info { "TCP Receiver started on port 5002" } + } else { + Logger.error { "Failed to start TCP Receiver on port 5002" } + } + + + val androidserver = TCP_Android_Command_Server() androidserver.StartTcpServer(5003){ Logger.info { it } @@ -182,14 +193,18 @@ fun main() { } } + db.Add_Log("AAS"," Application started") + // shutdown hook Runtime.getRuntime().addShutdownHook(Thread { + db.Add_Log("AAS"," Application stopping") Logger.info { "Shutdown hook called, stopping services..." } barixserver.StopTcpCommand() androidserver.StopTcpCommand() onlinechecker.cancel() web.Stop() udpreceiver.Stop() + tcpreceiver.Stop() audioPlayer.Close() db.close() Logger.info { "All services stopped, exiting application." } diff --git a/src/MainExtension01.kt b/src/MainExtension01.kt index 8599bbe..d5edfbd 100644 --- a/src/MainExtension01.kt +++ b/src/MainExtension01.kt @@ -183,7 +183,9 @@ class MainExtension01 { */ fun Get_Soundbank_Data(value: String) : Map? { if (ValidString(value)){ + //println("Parsing soundbank data from value: $value") val values = value.split(" ").map { it.trim() }.filter { ValidString(it) } + //println("Split into values: $values") if (values.isNotEmpty()){ val result = mutableMapOf() values.forEach { @@ -192,6 +194,7 @@ class MainExtension01 { // ada separator val key = it.substring(0, separatorindex).trim().uppercase() val value = it.substring(separatorindex+1).trim().uppercase() + //println("Got $key : $value") if (ValidString(key) && ValidString(value)){ if (SoundbankKeywords.contains(key)) result[key] = value } @@ -338,11 +341,9 @@ class MainExtension01 { files.add(city.Path) } else { return Result_GetSoundbankFiles(false, "Invalid soundbank file ${city.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") - } } else { return Result_GetSoundbankFiles(false, "No City found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") - } } } else { @@ -537,7 +538,7 @@ class MainExtension01 { fun Read_Queue_Paging() : Boolean{ db.queuepagingDB.Get() val list = db.queuepagingDB.List - .filter { it.Source=="PAGING" } + .filter { it.Type=="PAGING" } list.forEach { qp -> println("Processing $qp") @@ -593,7 +594,7 @@ class MainExtension01 { fun Read_Queue_Shalat() : Boolean{ db.queuepagingDB.Get() val list = db.queuepagingDB.List - .filter { it.Source=="SHALAT" } + .filter { it.Type=="SHALAT" } list.forEach { qp -> println("Processing $qp") if (qp.BroadcastZones.isNotBlank()){ @@ -852,13 +853,15 @@ class MainExtension01 { if (listafi.isNotEmpty()){ db.queuetableDB.DeleteByIndex(qa.index.toInt()) - - val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("Soundbank","")).toString() - println("Writing to target WAV file: $targetfile") + // bikin nama file ada postifx nya dari SB_TAGS, tapi spasi diganti underscore + // dan titik dua diganti dash + val postfix = qa.SB_TAGS.replace(" ","_").replace(":","-") + val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("${qa.Source}_${qa.Type}",postfix)).toString() + //println("Writing to target WAV file: $targetfile") val result = audioPlayer.WavWriter(listafi, targetfile, true) if (result.success){ // file siap broadcast - println("Successfully wrote WAV file: $targetfile") + //println("Successfully wrote WAV file: $targetfile") val targetafi = audioPlayer.LoadAudioFile(targetfile) if (targetafi.isValid()) { ips.forEach { ip -> StreamerOutputs[ip]?.SendData(targetafi.bytes, { db.Add_Log("AAS", it) }, { db.Add_Log("AAS", it) }) } diff --git a/src/audio/AudioPlayer.kt b/src/audio/AudioPlayer.kt index fa00e90..730cb62 100644 --- a/src/audio/AudioPlayer.kt +++ b/src/audio/AudioPlayer.kt @@ -143,8 +143,7 @@ class AudioPlayer (var samplingrate: Int) { mem.write(0, data, 0, data.size) val pushresult = bass.BASS_StreamPutData(streamhandle, mem, data.size) if (pushresult==-1){ - val errcode = bass.BASS_ErrorGetCode() - println("BASS_StreamPutData failed: $errcode") + Logger.error { "BASS_StreamPutData failed: ${bass.BASS_ErrorGetCode()}" } } return pushresult != -1 } @@ -154,32 +153,27 @@ class AudioPlayer (var samplingrate: Int) { if (withChime){ val chup = contentCache.getAudioFile("chimeup") if (chup!=null && chup.isValid()){ - if (pushData(chup.bytes)){ - println("Chime up pushed") - } else { + if (!pushData(chup.bytes)){ all_success = false - println("Chime up failed") + Logger.error { "Failed to push Chime Up" } } - } else println("Chime Up not valid") + } else Logger.error { "withChime=true, but Chime Up not available" } } - if (pushData(data)){ - println("Data pushed") - } else { + if (!pushData(data)){ all_success = false - println("Data push failed") + Logger.error { "Failed to push Data ByteArray" } } + if (withChime){ val chdn = contentCache.getAudioFile("chimedown") if (chdn!=null && chdn.isValid()){ - if (pushData(chdn.bytes)){ - println("Chime down pushed") - } else { + if (!pushData(chdn.bytes)){ all_success = false - println("Chime down failed") + Logger.error { "Failed to push Chime Down" } } - } else println("Chime Down not valid") + } else Logger.error { "withChime=true, but Chime Down not available" } } val readsize: Long = 1024 * 1024 // read 1 MB at a time @@ -191,7 +185,6 @@ class AudioPlayer (var samplingrate: Int) { totalread += read } } while (read > 0) - println("Finished reading stream data, total $totalread bytes read") bassenc.BASS_Encode_Stop(encodehandle) bass.BASS_StreamFree(streamhandle) @@ -246,8 +239,7 @@ class AudioPlayer (var samplingrate: Int) { mem.write(0, data, 0, data.size) val pushresult = bass.BASS_StreamPutData(streamhandle, mem, data.size) if (pushresult==-1){ - val errcode = bass.BASS_ErrorGetCode() - println("BASS_StreamPutData failed: $errcode") + Logger.error { "BASS_StreamPutData failed: ${bass.BASS_ErrorGetCode()}" } } return pushresult != -1 } @@ -258,39 +250,34 @@ class AudioPlayer (var samplingrate: Int) { if (withChime){ val chup = contentCache.getAudioFile("chimeup") if (chup!=null && chup.isValid()){ - if (pushData(chup.bytes)){ - println("Chime up pushed") - } else { + if (!pushData(chup.bytes)){ allsuccess = false - println("Chime up failed") + Logger.error { "Failed to push Chime Up" } } - } else println("Chime Up not valid") + } else Logger.error { "withChime=true, but Chime Up not available" } } sources.forEach { source -> if (source.isValid()) { // write the bytes to the stream - if (pushData(source.bytes)){ - println("Source ${source.fileName} pushed") - } else { + if (!pushData(source.bytes)){ allsuccess = false - println("Source ${source.fileName} push failed") + Logger.error { "Source ${source.fileName} push failed" } } + } else { allsuccess = false - println("Source ${source.fileName} is not valid") + Logger.error { "Not pushing Source=${source.fileName} because invalid" } } } if (withChime){ val chdn = contentCache.getAudioFile("chimedown") if (chdn!=null && chdn.isValid()){ - if (pushData(chdn.bytes)){ - println("Chime down pushed") - } else { + if (!pushData(chdn.bytes)){ allsuccess = false - println("Chime down failed") + Logger.error { "Failed to push Chime Down" } } - } else println("Chime Down not valid") + } else Logger.error { "withChime=true, but Chime Down not available"} } val readsize: Long = 1024 * 1024 // read 1 MB at a time @@ -302,7 +289,6 @@ class AudioPlayer (var samplingrate: Int) { totalread += read } } while (read > 0) - println("Finished reading stream data, total $totalread bytes read") // close the encoding handle diff --git a/src/audio/TCPReceiver.kt b/src/audio/TCPReceiver.kt new file mode 100644 index 0000000..39d3c4f --- /dev/null +++ b/src/audio/TCPReceiver.kt @@ -0,0 +1,104 @@ +package audio + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.tinylog.Logger +import java.net.ServerSocket +import java.util.function.Consumer + +/** + * TCPReceiver is a class that listens for TCP connections on a specified port. + * for receiving PCMFILE from Android SAMI + */ +class TCPReceiver(val portnumber: Int = 5002){ + private lateinit var server: ServerSocket + private var isRunning = false + private val dataCallback = mutableMapOf>() + private val isfinishd = mutableMapOf() + + /** + * Start listening for TCP connections on the specified port. + * @return true if successful, false otherwise + */ + fun Start() : Boolean{ + try{ + server = ServerSocket(portnumber) + isRunning = true + Logger.info { "Server started at port $portnumber" } + CoroutineScope(Dispatchers.IO).launch { + while(isRunning){ + try { + val client = server.accept() + val clientAddress = client.inetAddress.hostAddress + CoroutineScope(Dispatchers.IO).launch { + + isfinishd[clientAddress] = false + var totalbytes = 0L + try{ + val din = client.getInputStream() + Logger.info{ "Start receiving PCMFILE from Android with IP=${clientAddress}" } + + do { + val buffer = ByteArray(16384) + val bytesRead = din.read(buffer) + if (bytesRead>0){ + val data = ByteArray(bytesRead) + System.arraycopy(buffer, 0, data, 0, bytesRead) + //println("Received $bytesRead bytes from $clientAddress") + totalbytes+=bytesRead + dataCallback[clientAddress].let { + it?.accept(data) + } + } + } while (bytesRead > 0) + } catch (e : Exception){ + Logger.error { "Failed receiving data from $clientAddress, Message : ${e.message}" } + } + Logger.info { "Connection from $clientAddress ended, total bytesRead=$totalbytes" } + isfinishd[clientAddress] = true + } + } catch (e: Exception) { + Logger.error { "Failed to accept socket, Message : ${e.message}" } + } + } + } + return true + } catch (e : Exception){ + Logger.error { "Failed to Start Server at port $portnumber, Message : ${e.message}" } + return false + } + } + + fun RequestDataFrom(ipaddress: String, cb: Consumer){ + dataCallback[ipaddress] = cb + } + + fun StopRequestDataFrom(ipaddress: String){ + if (isfinishd[ipaddress] != null){ + if (isfinishd[ipaddress]==false){ + // belum selesai + //println("Waiting for receiving from $ipaddress to finish...") + runBlocking { + while (isfinishd[ipaddress] == false){ + kotlinx.coroutines.delay(100) + } + } + } + //println("Removing callback for $ipaddress") + dataCallback.remove(ipaddress) + } + + } + + /** + * Stop listening for TCP connections and close the server socket. + */ + fun Stop(){ + if (isRunning){ + isRunning = false + server.close() + } + } +} diff --git a/src/commandServer/PagingJob.kt b/src/commandServer/PagingJob.kt index 4d8b483..ac00a3b 100644 --- a/src/commandServer/PagingJob.kt +++ b/src/commandServer/PagingJob.kt @@ -2,7 +2,6 @@ package commandServer import codes.Somecodes.Companion.PagingResult_directory import codes.Somecodes.Companion.filenameformat -import org.tinylog.Logger import java.io.ByteArrayOutputStream import java.nio.file.Path import java.time.LocalDateTime @@ -18,6 +17,11 @@ class PagingJob(val fromIP: String, val broadcastzones: String) { var totalBytesReceived = 0; private set var isRunning = true; private set + /** + * Expected Size from PCMFILE android + */ + var expectedSize = 0 + /** * Adds incoming audio data to the job. diff --git a/src/commandServer/TCP_Android_Command_Server.kt b/src/commandServer/TCP_Android_Command_Server.kt index edced9f..e7d47a0 100644 --- a/src/commandServer/TCP_Android_Command_Server.kt +++ b/src/commandServer/TCP_Android_Command_Server.kt @@ -3,6 +3,7 @@ package commandServer import audioPlayer import codes.Somecodes.Companion.ValidString import codes.Somecodes.Companion.datetimeformat1 +import content.Category import content.Language import database.Messagebank import database.QueuePaging @@ -16,6 +17,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.tinylog.Logger +import tcpreceiver import udpreceiver import java.net.ServerSocket import java.net.Socket @@ -167,102 +169,158 @@ class TCP_Android_Command_Server { } } - "PCMFILE_START","STARTPAGINGAND" -> { - //TODO Paging IPM1 success, Paging IPMT gagal + "PCMFILE_START" ->{ + // start sending PCM data from Android for paging + val size = parts.getOrElse(1) { "0" }.toInt() + val filename = parts.getOrElse(2) { "" } + val zones = parts.getOrElse(3) { "" }.replace(",",";") + if (size>0){ + if (ValidString(filename)){ + if (ValidString(zones)){ + // create paging job + val pj = PagingJob(key, zones) + // ada expected size + pj.expectedSize = size + // masukin ke list + listOnGoingPaging[key] = pj + Logger.info{"PagingJob created for Android $key, zones: $zones, file: ${pj.filePath.absolutePathString()}"} + + tcpreceiver.RequestDataFrom(key) { + // push data ke paging job + pj.addData(it, it.size) + } + cb.accept("PCMFILE_START;OK@") + Logger.info{"Android $key start sending PCM data, expecting $size bytes"} + return + + } else logcb.accept("PCMFILE_START from Android $key failed, empty zones") + } else logcb.accept("PCMFILE_START from Android $key failed, empty filename") + } else logcb.accept("PCMFILE_START from Android $key failed, invalid size") + cb.accept("PCMFILE_START;NG@") + } + "PCMFILE_STOP" -> { + // stop sending PCM data from Android for paging + val pj = listOnGoingPaging[key] + if (pj!=null) { + listOnGoingPaging.remove(key) + tcpreceiver.StopRequestDataFrom(key) + // get remaining data + val data = pj.GetData() + pj.Close() + if (data.size==pj.expectedSize){ + Logger.info { "Paging job closed from Android $key, total bytes received ${data.size}, writing to file ${pj.filePath.absolutePathString()}" } + val result = audioPlayer.WavWriter(data, pj.filePath.absolutePathString(), true) + if (result.success) { + val qp = QueuePaging( + 0u, + LocalDateTime.now().format(datetimeformat1), + "ANDROID", + "PAGING", + pj.filePath.absolutePathString(), + pj.broadcastzones + ) + if (db.queuepagingDB.Add(qp)) { + logcb.accept("Paging audio inserted to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}") + cb.accept("PCMFILE_STOP;OK@") + return + } else logcb.accept("Failed to insert paging audio to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}") + } else logcb.accept("Failed to write paging audio to file ${pj.filePath.absolutePathString()}, Message : ${result.message}") + } else logcb.accept("PCMFILE_STOP from Android $key received size ${data.size} does not match expected ${pj.expectedSize}") + } else logcb.accept("PCMFILE_STOP from Android $key failed, no ongoing PCM data receiving") + cb.accept("PCMFILE_STOP;NG@") + } + + "STARTPAGINGAND" -> { + // Start Paging request from IPM val zones = parts.getOrElse(1) { "" }.replace(",",";") if (ValidString(zones)){ // create pagingjob val pj = PagingJob(key, zones) // masukin ke list listOnGoingPaging[key] = pj - Logger.info{"PagingJob created for Android $key, zones: $zones, file: ${pj.filePath.absolutePathString()}"} + Logger.info{"PagingJob created for IPM $key, zones: $zones, file: ${pj.filePath.absolutePathString()}"} // start minta data dari udpreceiver udpreceiver.RequestDataFrom(key){ // push data ke paging job pj.addData(it, it.size) } - logcb.accept("Paging started from Android $key") - cb.accept(parts[0]+";OK@") + logcb.accept("Paging started from IPM $key") + cb.accept("STARTPAGINGAND;OK@") return - } else logcb.accept("Paging start from Android $key failed, empty zones") - cb.accept(parts[0]+";NG@") + } else logcb.accept("Paging start from IPM $key failed, empty zones") + cb.accept("STARTPAGINGAND;NG@") } - "PCMFILE_STOP","STOPPAGINGAND" -> { - // TODO Paging IPM1 success, Paging IPMT gagal + "STOPPAGINGAND" -> { + // stop paging request from IPM val pj = listOnGoingPaging[key] if (pj!=null){ listOnGoingPaging.remove(key) udpreceiver.StopRequestDataFrom(key) - logcb.accept("Paging stopped from Android $key") - cb.accept(parts[0]+";OK@") + logcb.accept("Paging stopped from IPM $key") // get remaining data val data = pj.GetData() pj.Close() - Logger.info{"Paging job closed from Android $key, total bytes received ${data.size}, writing to file ${pj.filePath.absolutePathString()}"} + Logger.info{"Paging job closed from IPM $key, total bytes received ${data.size}, writing to file ${pj.filePath.absolutePathString()}"} val result = audioPlayer.WavWriter(data, pj.filePath.absolutePathString(), true) if (result.success){ val qp = QueuePaging( 0u, LocalDateTime.now().format(datetimeformat1), + "IPM", "PAGING", - "NORMAL", pj.filePath.absolutePathString(), pj.broadcastzones ) if (db.queuepagingDB.Add(qp)){ - logcb.accept("Paging audio inserted to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}") - cb.accept(parts[0]+";OK@") - } else { - logcb.accept("Failed to insert paging audio to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}") - cb.accept(parts[0]+";NG@") - } - } else { - logcb.accept("Failed to write paging audio to file ${pj.filePath.absolutePathString()}, Message : ${result.message}") - cb.accept(parts[0]+";NG@") - return - } - - - } else { - logcb.accept("Paging stop from Android $key failed, no ongoing paging") - cb.accept(parts[0]+";NG@") - } + logcb.accept("Paging audio inserted to queue paging table from IPM $key, file ${pj.filePath.absolutePathString()}") + cb.accept("STOPPAGINGAND;OK@") + return + } else logcb.accept("Failed to insert paging audio to queue paging table from IPM $key, file ${pj.filePath.absolutePathString()}") + } else logcb.accept("Failed to write paging audio to file ${pj.filePath.absolutePathString()}, Message : ${result.message}") + } else logcb.accept("Paging stop from IPM $key failed, no ongoing paging") + cb.accept("STOPPAGINGAND;NG@") } "CANCELPAGINGAND" -> { - // TODO Cancel Paging IPM1 success, IPMT gagal + // cancel paging request from IPM val pj = listOnGoingPaging[key] if (pj!=null){ pj.Close() listOnGoingPaging.remove(key) udpreceiver.StopRequestDataFrom(key) - logcb.accept("Paging from Android $key cancelled") + logcb.accept("Paging from IPM $key cancelled") cb.accept("CANCELPAGINGAND;OK@") return - } else logcb.accept("Paging cancel from Android $key failed, no ongoing paging") + } else logcb.accept("Paging cancel from IPM $key failed, no ongoing paging") cb.accept("CANCELPAGINGAND;NG@") } "STARTINITIALIZE" -> { - // TODO STARTINITIALIZE IPM1 success, IPMT gagal val username = parts.getOrElse(1) { "" } if (ValidString(username)){ val userlogin = listUserLogin.find { it.username == username } if (userlogin != null){ val userdb = db.userDB.List.find { it.username == username } if (userdb != null){ + println("Sending initialization data to $key with username $username") val result = StringBuilder() + // kirim Zone result.append("ZONE") userdb.broadcastzones.split(";").map { it.trim() }.filter { it.isNotBlank() }.forEach { result.append(";") result.append(it) } result.append("@") + cb.accept(result.toString()) + + // kirim MSGTOTAL + result.clear() val VARMESSAGES = mutableListOf() + result.append("MSGTOTAL;") userdb.messagebank_ann_id // messagebank_ann_id adalah rentengan ANN_ID (digit) yang dipisah dengan ; .split(";") @@ -270,112 +328,272 @@ class TCP_Android_Command_Server { .map { it.trim() } // bukan string kosong antar dua tanda ; .filter { it.isNotBlank() } - // beneran digit semua - .filter { xx -> xx.all{it.isDigit()} } // iterasi setiap ANN_ID .forEach { annid -> // masukin ke VARMESSAGES yang unik secara ANN_ID dan Language val xx = db.messageDB.List .filter{ it.ANN_ID == annid.toUInt() } - .distinctBy { it.Language } + .distinctBy { it.ANN_ID } VARMESSAGES.addAll(xx) } - result.append("MSGTOTAL;").append(VARMESSAGES.size).append("@") - // VAR AP TOTAL - val VARAPTOTAL = mutableListOf() - val al_split = userdb.airline_tags.split(";").map { it.trim() }.filter { it.isNotBlank() } - al_split.forEach { - val sb = db.Find_Soundbank_AirplaneName(it).firstOrNull() - if (sb != null) VARAPTOTAL.add(sb) - } - result.append("VARAPTOTAL;").append(VARAPTOTAL.size).append("@") - // VAR CITY TOTAL - val VARCITYTOTAL = mutableListOf() - val ct_split = userdb.city_tags.split(";").map { it.trim() }.filter { it.isNotBlank() } - ct_split.forEach { - val sb = db.Find_Soundbank_City(it).firstOrNull() - if (sb != null) VARCITYTOTAL.add(sb) - } - result.append("VARCITYTOTAL;").append(VARCITYTOTAL.size).append("@") - // VAR PLACES TOTAL - val VARPLACESTOTAL = mutableListOf() -// sb_split.forEach { -// val sb = db.Find_Soundbank_Places(it).firstOrNull() -// if (sb != null) VARPLACESTOTAL.add(sb) -// } - result.append("VARPLACESTOTAL;").append(VARPLACESTOTAL.size).append("@") - // VAR SHALAT TOTAL - val VARSHALATTOTAL = mutableListOf() -// sb_split.forEach { -// val sb = db.Find_Soundbank_Shalat(it).firstOrNull() -// if (sb != null) VARSHALATTOTAL.add(sb) -// } - result.append("VARSHALATTOTAL;").append(VARSHALATTOTAL.size).append("@") - // VAR SEQUENCE TOTAL - val VARSEQUENCETOTAL = mutableListOf() -// sb_split.forEach { -// val sb = db.Find_Soundbank_Sequence(it).firstOrNull() -// if (sb != null) VARSEQUENCETOTAL.add(sb) -// } - // VAR REASON TOTAL - val VARREASONTOTAL = mutableListOf() -// sb_split.forEach { -// val sb = db.Find_Soundbank_Reason(it).firstOrNull() -// if (sb != null) VARREASONTOTAL.add(sb) -// } - result.append("VARREASONTOTAL;").append(VARREASONTOTAL.size).append("@") - // VAR PROCEDURE TOTAL - val VARPROCEDURETOTAL = mutableListOf() -// sb_split.forEach { -// val sb = db.Find_Soundbank_Procedure(it).firstOrNull() -// if (sb != null) VARPROCEDURETOTAL.add(sb) -// } - result.append("VARPROCEDURETOTAL;").append(VARPROCEDURETOTAL.size).append("@") - // send to sender + result.append(VARMESSAGES.size).append("@") cb.accept(result.toString()) + // kirim VARAPTOTAL result.clear() + result.append("VARAPTOTAL;") + val VARAPTOTAL = mutableListOf() + userdb.airline_tags + .split(";") + .map { it.trim() } + .filter { it.isNotBlank() } + .forEach { al -> + val sb = db.soundDB.List + .filter { it.Category.equals(Category.Airplane_Name.name, true) } + .filter { it.TAG.equals(al, true)} + .distinctBy { it.TAG } + VARAPTOTAL.addAll(sb) + } + result.append(VARAPTOTAL.size).append("@") + cb.accept(result.toString()) + + // kirim VARCITYTOTAL + result.clear() + result.append("VARCITYTOTAL;") + val VARCITYTOTAL = mutableListOf() + userdb.city_tags + .split(";") + .map { it.trim() } + .filter { it.isNotBlank() } + .forEach { ct -> + val sb = db.soundDB.List + .filter { it.Category.equals(Category.City.name, true) } + .filter { it.TAG.equals(ct, true)} + .distinctBy { it.TAG } + VARCITYTOTAL.addAll(sb) + } + result.append(VARCITYTOTAL.size).append("@") + cb.accept(result.toString()) + + // kirim VARPLACESTOTAL + result.clear() + result.append("VARPLACESTOTAL;") + val VARPLACESTOTAL = mutableListOf() + db.soundDB.List + .filter { it.Category.equals(Category.Places.name, true) } + .distinctBy { it.TAG } + .forEach { + VARPLACESTOTAL.add(it) + } + result.append(VARPLACESTOTAL.size).append("@") + cb.accept(result.toString()) + + // kirim VARSHALATTOTAL + result.clear() + result.append("VARSHALATTOTAL;") + val VARSHALATTOTAL = mutableListOf() + db.soundDB.List + .filter { it.Category.equals(Category.Shalat.name, true) } + .distinctBy { it.TAG } + .forEach { + VARSHALATTOTAL.add(it) + } + result.append(VARSHALATTOTAL.size).append("@") + cb.accept(result.toString()) + + // kirim VARSEQUENCETOTAL + result.clear() + result.append("VARSEQUENCETOTAL;") + val VARSEQUENCETOTAL = mutableListOf() + db.soundDB.List + .filter { it.Category.equals(Category.Sequence.name, true) } + .distinctBy { it.TAG } + .forEach { + VARSEQUENCETOTAL.add(it) + } + result.append(VARSEQUENCETOTAL.size).append("@") + cb.accept(result.toString()) + + // kirim VARREASONTOTAL + result.clear() + result.append("VARREASONTOTAL;") + val VARREASONTOTAL = mutableListOf() + db.soundDB.List + .filter { it.Category.equals(Category.Reason.name, true) } + .distinctBy { it.TAG } + .forEach { + VARREASONTOTAL.add(it) + } + result.append(VARREASONTOTAL.size).append("@") + cb.accept(result.toString()) + + // kirim VARPROCEDURETOTAL + val VARPROCEDURETOTAL = mutableListOf() + result.clear() + result.append("VARPROCEDURETOTAL;") + db.soundDB.List + .filter { it.Category.equals(Category.Procedure.name, true) } + .distinctBy { it.TAG } + .forEach { + VARPROCEDURETOTAL.add(it) + } + result.append(VARPROCEDURETOTAL.size).append("@") + cb.accept(result.toString()) + + // kirim VARGATETOTAL + val VARGATETOTAL = mutableListOf() + result.clear() + result.append("VARGATETOTAL;") + db.soundDB.List + .filter { it.Category.equals(Category.Gate.name, true) } + .distinctBy { it.TAG } + .forEach { + VARGATETOTAL.add(it) + } + result.append(VARGATETOTAL.size).append("@") + cb.accept(result.toString()) + + // kirim VARCOMPENSATIONTOTAL + result.clear() + result.append("VARCOMPENSATIONTOTAL;") + val VARCOMPENSATIONTOTAL = mutableListOf() + db.soundDB.List + .filter { it.Category.equals(Category.Compensation.name, true) } + .distinctBy { it.TAG } + .forEach { + VARCOMPENSATIONTOTAL.add(it) + } + result.append(VARCOMPENSATIONTOTAL.size).append("@") + cb.accept(result.toString()) + + // kirim VARGREETINGTOTAL + result.clear() + result.append("VARGREETINGTOTAL;") + val VARGREETINGTOTAL = mutableListOf() + db.soundDB.List + .filter { it.Category.equals(Category.Greeting.name, true) } + .distinctBy { it.TAG } + .forEach { + VARGREETINGTOTAL.add(it) + } + result.append(VARGREETINGTOTAL.size).append("@") + cb.accept(result.toString()) //Append MSG, for Android only Indonesia and English - VARMESSAGES.groupBy { it.ANN_ID }.forEach { (ann_id, value) -> - result.append("MSG;").append(ann_id) - result.append(";") - value.find { it.Language.equals(Language.INDONESIA.name, true) }?.let {result.append(it.Message_Detail)} ?: result.append("NA") - result.append(";") - value.find {it.Language.equals(Language.ENGLISH.name, true) }?.let {result.append(it.Message_Detail)} ?: result.append("NA") - result.append("@") + if (VARMESSAGES.isNotEmpty()) { + result.clear() + VARMESSAGES.forEachIndexed { index, msg -> + + val ann_id = msg.ANN_ID + val msg_indo = db.messageDB.List.find { + it.ANN_ID == ann_id && it.Language.equals( + Language.INDONESIA.name, + true + ) + } + val msg_eng = db.messageDB.List.find { + it.ANN_ID == ann_id && it.Language.equals( + Language.ENGLISH.name, + true + ) + } + val description = msg_indo?.Description ?: msg_eng?.Description ?: "UNKNOWN" + result.append("MSG;$index;$ann_id;$description;") + result.append(msg_indo?.Message_Detail ?:"").append(";") + result.append(msg_eng?.Message_Detail ?:"").append("@") + } + cb.accept(result.toString()) } // append VARAP - VARAPTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank -> - result.append("VARAP;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@") + if (VARAPTOTAL.isNotEmpty()) { + result.clear() + VARAPTOTAL.forEachIndexed { index, sb -> + result.append("VARAP;$index;${sb.TAG};${sb.Description}@") + } + cb.accept(result.toString()) } + // append VARCITY - VARCITYTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank -> - result.append("VARCITY;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@") + if (VARCITYTOTAL.isNotEmpty()) { + result.clear() + VARCITYTOTAL.forEachIndexed { index, sb -> + result.append("VARCITY;$index;${sb.TAG};${sb.Description}@") + } + cb.accept(result.toString()) } + // append VARPLACES - VARPLACESTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank -> - result.append("VARPLACES;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@") + if (VARPLACESTOTAL.isNotEmpty()) { + result.clear() + VARPLACESTOTAL.forEachIndexed { index, sb -> + result.append("VARPLACES;$index;${sb.TAG};${sb.Description}@") + } + cb.accept(result.toString()) } // append VARSHALAT - VARSHALATTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank -> - result.append("VARSHALAT;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@") + if (VARSHALATTOTAL.isNotEmpty()) { + result.clear() + VARSHALATTOTAL.forEachIndexed { index, sb -> + result.append("VARSHALAT;$index;${sb.TAG};${sb.Description}@") + } + cb.accept(result.toString()) } + // append VARSEQUENCE - VARSEQUENCETOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank -> - result.append("VARSEQUENCE;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@") + if (VARSEQUENCETOTAL.isNotEmpty()) { + result.clear() + VARSEQUENCETOTAL.forEachIndexed { index, sb -> + result.append("VARSEQUENCE;$index;${sb.TAG};${sb.Description}@") + } + cb.accept(result.toString()) } + // append VARREASON - VARREASONTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank -> - result.append("VARREASON;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@") + if (VARREASONTOTAL.isNotEmpty()) { + result.clear() + VARREASONTOTAL.forEachIndexed { index, sb -> + result.append("VARREASON;$index;${sb.TAG};${sb.Description}@") + } + cb.accept(result.toString()) } + // append VARPROCEDURE - VARPROCEDURETOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank -> - result.append("VARPROCEDURE;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@") + if (VARPROCEDURETOTAL.isNotEmpty()) { + result.clear() + VARPROCEDURETOTAL.forEachIndexed { index, sb -> + result.append("VARPROCEDURE;$index;${sb.TAG};${sb.Description}@") + } + cb.accept(result.toString()) } - // send to sender - cb.accept(result.toString()) + + // append VARGATE + if (VARGATETOTAL.isNotEmpty()) { + result.clear() + VARGATETOTAL.forEachIndexed { index, sb -> + result.append("VARGATE;$index;${sb.TAG};${sb.Description}@") + } + cb.accept(result.toString()) + } + + // append VARCOMPENSATION + if (VARCOMPENSATIONTOTAL.isNotEmpty()) { + result.clear() + VARCOMPENSATIONTOTAL.forEachIndexed { index, sb -> + result.append("VARCOMPENSATION;$index;${sb.TAG};${sb.Description}@") + } + cb.accept(result.toString()) + } + + // append VARGREETING + if (VARGREETINGTOTAL.isNotEmpty()) { + result.clear() + VARGREETINGTOTAL.forEachIndexed { index, sb -> + result.append("VARGREETING;$index;${sb.TAG};${sb.Description}@") + } + cb.accept(result.toString()) + } + logcb.accept("All variables sent to $key with username $username") return } else logcb.accept("STARTINITIALIZE failed from $key with username $username not found in userDB") @@ -387,8 +605,11 @@ class TCP_Android_Command_Server { "BROADCASTAND" -> { // semi auto dari android, masukin ke queue table val desc = parts.getOrElse(1) { "" } + // language bisa lebih dari satu, dipisah dengan koma val lang = parts.getOrElse(2) { "" }.replace(",",";") + // tags bisa lebih dari satu, dipisah dengan spasi val tags = parts.getOrElse(3) { "" }.replace(",",";") + // zone bisa lebih dari satu, dipisah dengan koma val zone = parts.getOrElse(4) { "" }.replace(",",";") if (ValidString(desc)){ if (ValidString(lang)){ @@ -414,7 +635,7 @@ class TCP_Android_Command_Server { } else logcb.accept("Broadcsast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty tags") } else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty language") } else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty description") - cb.accept("NG@") + cb.accept("BROADCASTAND;NG@") } diff --git a/src/content/Category.kt b/src/content/Category.kt index 771918c..dc4f965 100644 --- a/src/content/Category.kt +++ b/src/content/Category.kt @@ -14,5 +14,8 @@ enum class Category(name: String) { Birthday("Birthday"), Reason("Reason"), Sequence("Sequence"), - Procedure("Procedure"); + Procedure("Procedure"), + Gate("Gate"), + Greeting("Greeting"), + Compensation("Compensation"); } \ No newline at end of file diff --git a/src/database/MariaDB.kt b/src/database/MariaDB.kt index 8c252dd..8c9d6b5 100644 --- a/src/database/MariaDB.kt +++ b/src/database/MariaDB.kt @@ -1179,7 +1179,7 @@ class MariaDB( override fun Add(data: QueueTable): Boolean { try { val statement = connection.prepareStatement( - "INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" + "INSERT INTO ${super.dbName} (`Date_Time`, `Source`, `Type`, `Message`, `SB_TAGS`, `BroadcastZones`, `Repeat`, `Language`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" ) statement?.setString(1, data.Date_Time) statement?.setString(2, data.Source) @@ -1191,7 +1191,7 @@ class MariaDB( statement?.setString(8, data.Language) val rowsAffected = statement?.executeUpdate() if (rowsAffected != null && rowsAffected > 0) { - Logger.info("QueueTable added: ${data.Message}" as Any) + Logger.info("QueueTable added Source=${data.Source} Type=${data.Type} Message=${data.Message}, Languages=${data.Language} Variables=${data.SB_TAGS}, BroadcastZones=${data.BroadcastZones}" as Any) return true } else { Logger.warn("No QueueTable entry added for: ${data.Message}" as Any) @@ -1206,7 +1206,7 @@ class MariaDB( try { connection.autoCommit = false val sql = - "INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" + "INSERT INTO ${super.dbName} (`Date_Time`, `Source`, `Type`, `Message`, `SB_TAGS`, `BroadcastZones`, `Repeat`, `Language`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" val statement = connection.prepareStatement(sql) for (qt in data) { statement.setString(1, qt.Date_Time)