From ea04f8d3168ed0c36ce308fc3c8bc422a392bf37 Mon Sep 17 00:00:00 2001 From: rdkartono Date: Wed, 3 Sep 2025 11:05:30 +0700 Subject: [PATCH] commit 03/09/2025 --- .idea/inspectionProfiles/Project_Default.xml | 1 + AAS_NewGen.iml | 18 + src/database/BroadcastZones.kt | 2 +- src/database/IpZones.kt | 2 +- src/database/LanguageLink.kt | 2 +- src/database/MariaDB.kt | 1071 ++++++++++++++++-- src/database/Messagebank.kt | 14 +- src/database/QueueFids.kt | 2 +- src/database/QueuePaging.kt | 2 +- src/database/QueueTable.kt | 2 +- src/database/ScheduleBank.kt | 2 +- src/database/Soundbank.kt | 14 +- src/database/UserDB.kt | 2 +- src/web/WebApp.kt | 348 ++++-- 14 files changed, 1272 insertions(+), 210 deletions(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 433f5a8..cc85ce1 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -6,6 +6,7 @@ + diff --git a/AAS_NewGen.iml b/AAS_NewGen.iml index cb9b844..f74f693 100644 --- a/AAS_NewGen.iml +++ b/AAS_NewGen.iml @@ -22,5 +22,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/database/BroadcastZones.kt b/src/database/BroadcastZones.kt index ff948e3..a952ed6 100644 --- a/src/database/BroadcastZones.kt +++ b/src/database/BroadcastZones.kt @@ -1,4 +1,4 @@ package database @Suppress("unused") -data class BroadcastZones(val index: UInt, val description: String, val SoundChannel: String, val Box: String, val Relay: String) +data class BroadcastZones(var index: UInt, var description: String, var SoundChannel: String, var Box: String, var Relay: String) diff --git a/src/database/IpZones.kt b/src/database/IpZones.kt index cc191b5..7debd6b 100644 --- a/src/database/IpZones.kt +++ b/src/database/IpZones.kt @@ -1,4 +1,4 @@ package database @Suppress("unused") -data class IpZones(val index:UInt, val description: String, val ip: String) +data class IpZones(var index:UInt, var description: String, var ip: String) diff --git a/src/database/LanguageLink.kt b/src/database/LanguageLink.kt index 552468e..540b9b4 100644 --- a/src/database/LanguageLink.kt +++ b/src/database/LanguageLink.kt @@ -1,4 +1,4 @@ package database @Suppress("unused") -data class LanguageLink(val index: UInt, val TAG: String, val Language: String) +data class LanguageLink(var index: UInt, var TAG: String, var Language: String) diff --git a/src/database/MariaDB.kt b/src/database/MariaDB.kt index 83d25e2..4152509 100644 --- a/src/database/MariaDB.kt +++ b/src/database/MariaDB.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.mariadb.jdbc.Connection import org.tinylog.Logger import java.sql.DriverManager @@ -19,19 +20,19 @@ import java.util.function.Consumer * @property password The password for the database connection. */ @Suppress("unused") -class MariaDB ( - address : String = "localhost", - port : Int = 3306, - dbName : String = "aas", - username : String = "admin", - password : String = "admin" +class MariaDB( + address: String = "localhost", + port: Int = 3306, + dbName: String = "aas", + username: String = "admin", + password: String = "admin" ) { - private var connection : Connection? = null - var connected : Boolean = false - var SoundbankList : ArrayList = ArrayList() - var MessagebankList : ArrayList = ArrayList() - var LanguageLinkList : ArrayList = ArrayList() - var SchedulebankList : ArrayList = ArrayList() + private var connection: Connection? = null + var connected: Boolean = false + var SoundbankList: ArrayList = ArrayList() + var MessagebankList: ArrayList = ArrayList() + var LanguageLinkList: ArrayList = ArrayList() + var SchedulebankList: ArrayList = ArrayList() companion object { fun ValidDate(date: String): Boolean { @@ -39,6 +40,7 @@ class MariaDB ( val regex = Regex("""^\d{2}/\d{2}/\d{4}$""") return regex.matches(date) } + fun ValidTime(time: String): Boolean { // Check if the time is in the format HH:MM:SS val regex = Regex("""^\d{2}:\d{2}:\d{2}$""") @@ -46,12 +48,13 @@ class MariaDB ( } private val objectMapper = jacksonObjectMapper() + /** * Convert SoundbankList, MessagebankList, LanguageLinkList, or SchedulebankList to a JSON String. * @param list The ArrayList to convert to a String. * @return A JSON String representation of the ArrayList. */ - fun ArrayListtoString(list: ArrayList) : String{ + fun ArrayListtoString(list: ArrayList): String { return try { objectMapper.writeValueAsString(list.toArray()) } catch (e: Exception) { @@ -64,12 +67,13 @@ class MariaDB ( init { try { - connection = DriverManager.getConnection("jdbc:mariadb://$address:$port/$dbName", username, password) as Connection + connection = + DriverManager.getConnection("jdbc:mariadb://$address:$port/$dbName", username, password) as Connection Logger.info("Connected to MariaDB" as Any) connected = true runBlocking { - withContext(Dispatchers.IO){ + withContext(Dispatchers.IO) { Reload_Messagebank() Reload_Soundbank() Reload_LanguageLink() @@ -79,14 +83,14 @@ class MariaDB ( - Logger.info { "Loading MariaDB completed" } - Logger.info { "Soundbank count: ${SoundbankList.size}" } - Logger.info { "Messagebank count: ${MessagebankList.size}" } - Logger.info { "LanguageLink count: ${LanguageLinkList.size}" } - Logger.info { "Schedulebank count: ${SchedulebankList.size}" } + Logger.info { "Loading MariaDB completed" } + Logger.info { "Soundbank count: ${SoundbankList.size}" } + Logger.info { "Messagebank count: ${MessagebankList.size}" } + Logger.info { "LanguageLink count: ${LanguageLinkList.size}" } + Logger.info { "Schedulebank count: ${SchedulebankList.size}" } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Failed to connect to MariaDB: ${e.message}" as Any) } } @@ -99,7 +103,7 @@ class MariaDB ( connection?.close() Logger.info("Connection to MariaDB closed" as Any) - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error closing MariaDB connection: ${e.message}" as Any) } connected = false @@ -110,14 +114,15 @@ class MariaDB ( * @param machine The machine name or identifier. * @param description The description of the log entry. */ - fun Add_Log(machine: String, description: String){ + fun Add_Log(machine: String, description: String) { val currentDate = java.time.LocalDate.now() val currentTime = java.time.LocalTime.now().withNano(0) // remove nanoseconds for cleaner format val dateString = currentDate.format(java.time.format.DateTimeFormatter.ofPattern("dd/MM/yyyy")) val timeString = currentTime.format(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss")) try { - val statement = connection?.prepareStatement("INSERT INTO log (datenya, timenya, machine, description) VALUES (?, ?, ?, ?)") + val statement = + connection?.prepareStatement("INSERT INTO log (datenya, timenya, machine, description) VALUES (?, ?, ?, ?)") statement?.setString(1, dateString) statement?.setString(2, timeString) statement?.setString(3, machine) @@ -128,16 +133,65 @@ class MariaDB ( } else { Logger.warn("No log entry added for: [$dateString $timeString] [$machine] $description" as Any) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error adding log entry: ${e.message}" as Any) } } + /** + * Exports the log table to an XLSX workbook for a specific date and optional filter. + * @param logDate The date string in format "dd/MM/yyyy". + * @param logFilter The filter string for the description or machine. If empty, exports all logs for the date. + * @return An XSSFWorkbook containing the filtered log data, or null if an error occurred. + */ + fun Export_Log_XLSX(logDate: String, logFilter: String): XSSFWorkbook? { + try { + val statement = connection?.prepareStatement( + if (logFilter.isBlank()) { + "SELECT * FROM log WHERE datenya = ?" + } else { + "SELECT * FROM log WHERE datenya = ? AND (description LIKE ? OR machine LIKE ?)" + } + ) + statement?.setString(1, logDate) + if (!logFilter.isBlank()) { + val filter = "%$logFilter%" + statement?.setString(2, filter) + statement?.setString(3, filter) + } + val resultSet = statement?.executeQuery() + val workbook = XSSFWorkbook() + val sheet = workbook.createSheet("Log") + val headerRow = sheet.createRow(0) + val headers = arrayOf("Index", "datenya", "timenya", "machine", "description") + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.createCell(colIndex) + cell.setCellValue(header) + } + var rowIndex = 1 + while (resultSet?.next() == true) { + val row = sheet.createRow(rowIndex++) + row.createCell(0).setCellValue(resultSet.getString("index")) + row.createCell(1).setCellValue(resultSet.getString("datenya")) + row.createCell(2).setCellValue(resultSet.getString("timenya")) + row.createCell(3).setCellValue(resultSet.getString("machine")) + row.createCell(4).setCellValue(resultSet.getString("description")) + } + for (i in headers.indices) { + sheet.autoSizeColumn(i) + } + return workbook + } catch (e: Exception) { + Logger.error { "Error exporting Log, Msg: ${e.message}" } + } + return null + } + /** * Get All Log from database * @param consumer A Consumer that will receive the list of logs */ - fun GetLog(consumer : Consumer>) { + fun GetLog(consumer: Consumer>) { val logList = ArrayList() try { val statement = connection?.createStatement() @@ -152,7 +206,7 @@ class MariaDB ( ) logList.add(log) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error fetching logs: ${e.message}" as Any) } consumer.accept(logList) @@ -163,9 +217,9 @@ class MariaDB ( * @param date The date to filter logs by (format: DD/MM/YYYY) * @param consumer A Consumer that will receive the list of logs for the specified date */ - fun GetLog(date : String, consumer: Consumer>) { + fun GetLog(date: String, consumer: Consumer>) { val logList = ArrayList() - if (ValidDate(date)){ + if (ValidDate(date)) { try { val statement = connection?.prepareStatement("SELECT * FROM log WHERE datenya = ?") statement?.setString(1, date) @@ -180,7 +234,7 @@ class MariaDB ( ) logList.add(log) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error fetching logs for date $date: ${e.message}" as Any) } } @@ -188,11 +242,12 @@ class MariaDB ( consumer.accept(logList) } - fun GetLog(date : String, filter : String, consumer: Consumer>) { + fun GetLog(date: String, filter: String, consumer: Consumer>) { val logList = ArrayList() - if (ValidDate(date)){ + if (ValidDate(date)) { try { - val statement = connection?.prepareStatement("SELECT * FROM log WHERE datenya = ? AND description LIKE ?") + val statement = + connection?.prepareStatement("SELECT * FROM log WHERE datenya = ? AND description LIKE ?") statement?.setString(1, date) statement?.setString(2, "%$filter%") val resultSet = statement?.executeQuery() @@ -206,7 +261,7 @@ class MariaDB ( ) logList.add(log) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error fetching logs for date $date with filter $filter: ${e.message}" as Any) } } @@ -236,17 +291,136 @@ class MariaDB ( ) SchedulebankList.add(schedulebank) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error fetching schedulebanks: ${e.message}" as Any) } } + /** + * Adds a new schedulebank entry to the database. + * @param schedulebank The ScheduleBank object to add. + * @return True if the addition was successful, false otherwise. + */ + fun Add_Schedulebank(schedulebank: ScheduleBank): Boolean { + if (!ValidDate(schedulebank.Day)) { + Logger.error("Error adding schedulebank entry: Invalid date format ${schedulebank.Day}" as Any) + return false + } + if (!ValidTime(schedulebank.Time)) { + Logger.error("Error adding schedulebank entry: Invalid time format ${schedulebank.Time}" as Any) + return false + } + try { + val statement = + connection?.prepareStatement("INSERT INTO schedulebank (Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones, Language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + statement?.setString(1, schedulebank.Description) + statement?.setString(2, schedulebank.Day) + statement?.setString(3, schedulebank.Time) + statement?.setString(4, schedulebank.Soundpath) + statement?.setInt(5, schedulebank.Repeat.toInt()) + statement?.setBoolean(6, schedulebank.Enable) + statement?.setString(7, schedulebank.BroadcastZones) + statement?.setString(8, schedulebank.Language) + val rowsAffected = statement?.executeUpdate() + if (rowsAffected != null && rowsAffected > 0) { + Logger.info("Schedulebank added: ${schedulebank.Description}" as Any) + return true + } else { + Logger.warn("No schedulebank entry added for: ${schedulebank.Description}" as Any) + } + } catch (e: Exception) { + Logger.error("Error adding schedulebank entry: ${e.message}" as Any) + } + return false + } + + /** + * Adds multiple schedulebank entries to the database using bulk insert. + * @param schedulebankList ArrayList of ScheduleBank objects to add. + * @return True if the addition was successful, false otherwise. + */ + fun Add_Schedulebank(schedulebankList: ArrayList): Boolean { + if (schedulebankList.isNotEmpty()) { + try { + if (connection != null) { + connection!!.autoCommit = false + val sql = + "INSERT INTO schedulebank (Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones, Language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" + val statement = connection!!.prepareStatement(sql) + for (sb in schedulebankList) { + if (!ValidDate(sb.Day) || !ValidTime(sb.Time)) { + Logger.error("Invalid date or time format for schedulebank: ${sb.Description}" as Any) + continue + } + statement.setString(1, sb.Description) + statement.setString(2, sb.Day) + statement.setString(3, sb.Time) + statement.setString(4, sb.Soundpath) + statement.setInt(5, sb.Repeat.toInt()) + statement.setBoolean(6, sb.Enable) + statement.setString(7, sb.BroadcastZones) + statement.setString(8, sb.Language) + statement.addBatch() + } + statement.executeBatch() + connection!!.commit() + Logger.info("Bulk schedulebank insert successful: ${schedulebankList.size} entries" as Any) + connection!!.autoCommit = true + return true + } + } catch (e: Exception) { + Logger.error("Error adding schedulebank entries: ${e.message}" as Any) + } + } + return false + } + + /** + * Updates an existing schedulebank entry in the database by its index. + * @param index The index of the schedulebank entry to update. + * @param schedulebank The ScheduleBank object with updated values. + * @return True if the update was successful, false otherwise. + */ + fun Update_Schedulebank_by_index(index: UInt, schedulebank: ScheduleBank): Boolean { + if (!ValidDate(schedulebank.Day)) { + Logger.error("Error updating schedulebank entry: Invalid date format ${schedulebank.Day}" as Any) + return false + } + if (!ValidTime(schedulebank.Time)) { + Logger.error("Error updating schedulebank entry: Invalid time format ${schedulebank.Time}" as Any) + return false + } + try { + val statement = + connection?.prepareStatement("UPDATE schedulebank SET Description = ?, Day = ?, Time = ?, Soundpath = ?, Repeat = ?, Enable = ?, BroadcastZones = ?, Language = ? WHERE `index` = ?") + statement?.setString(1, schedulebank.Description) + statement?.setString(2, schedulebank.Day) + statement?.setString(3, schedulebank.Time) + statement?.setString(4, schedulebank.Soundpath) + statement?.setInt(5, schedulebank.Repeat.toInt()) + statement?.setBoolean(6, schedulebank.Enable) + statement?.setString(7, schedulebank.BroadcastZones) + statement?.setString(8, schedulebank.Language) + statement?.setLong(9, index.toLong()) + val rowsAffected = statement?.executeUpdate() + if (rowsAffected != null && rowsAffected > 0) { + Logger.info("Schedulebank updated at index $index: ${schedulebank.Description}" as Any) + return true + } else { + Logger.warn("No schedulebank entry updated at index $index for: ${schedulebank.Description}" as Any) + } + } catch (e: Exception) { + Logger.error("Error updating schedulebank entry at index $index: ${e.message}" as Any) + } + return false + } + /** * Deletes a schedulebank entry by its index. * @param index The index of the schedulebank entry to delete. * @return True if the deletion was successful, false otherwise. */ - fun Delete_Schedulebank_by_index(index: UInt) : Boolean { + fun Delete_Schedulebank_by_index(index: UInt): Boolean { try { val statement = connection?.prepareStatement("DELETE FROM schedulebank WHERE `index` = ?") statement?.setLong(1, index.toLong()) @@ -257,7 +431,7 @@ class MariaDB ( } else { Logger.warn("No rows deleted from schedulebank with index $index" as Any) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error deleting from schedulebank with index $index: ${e.message}" as Any) } return false @@ -266,7 +440,7 @@ class MariaDB ( /** * Clears all entries from the schedulebank table in the database and the local list. */ - fun Clear_Schedulebank() : Boolean { + fun Clear_Schedulebank(): Boolean { try { val statement = connection?.createStatement() // use TRUNCATE to reset auto increment index @@ -274,12 +448,110 @@ class MariaDB ( Logger.info("Schedulebank table cleared" as Any) SchedulebankList.clear() return true - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error clearing schedulebank table: ${e.message}" as Any) } return false } + /** + * Exports the schedulebank table to an XLSX workbook. + * @return An XSSFWorkbook containing the schedulebank data, or null if an error occurred. + */ + fun Export_Schedulebank_XLSX(): XSSFWorkbook? { + try { + val statement = connection?.createStatement() + val resultSet = statement?.executeQuery("SELECT * FROM schedulebank") + val workbook = XSSFWorkbook() + val sheet = workbook.createSheet("Schedulebank") + val headerRow = sheet.createRow(0) + val headers = arrayOf( + "Index", + "Description", + "Day", + "Time", + "Soundpath", + "Repeat", + "Enable", + "BroadcastZones", + "Language" + ) + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.createCell(colIndex) + cell.setCellValue(header) + } + var rowIndex = 1 + while (resultSet?.next() == true) { + val row = sheet.createRow(rowIndex++) + row.createCell(0).setCellValue(resultSet.getString("index")) + row.createCell(1).setCellValue(resultSet.getString("Description")) + row.createCell(2).setCellValue(resultSet.getString("Day")) + row.createCell(3).setCellValue(resultSet.getString("Time")) + row.createCell(4).setCellValue(resultSet.getString("Soundpath")) + row.createCell(5).setCellValue(resultSet.getString("Repeat")) + row.createCell(6).setCellValue(resultSet.getString("Enable")) + row.createCell(7).setCellValue(resultSet.getString("BroadcastZones")) + row.createCell(8).setCellValue(resultSet.getString("Language")) + } + for (i in headers.indices) { + sheet.autoSizeColumn(i) + } + return workbook + } catch (e: Exception) { + Logger.error { "Error exporting Schedulebank, Msg: ${e.message}" } + } + return null + } + + /** + * Imports schedulebank entries from an XLSX workbook. + * @param workbook The XSSFWorkbook containing the schedulebank data. + * @return True if the import was successful, false otherwise. + */ + fun Import_Schedulebank_XLSX(workbook: XSSFWorkbook): Boolean { + try { + val sheet = workbook.getSheet("Schedulebank") ?: throw Exception("No sheet named 'Schedulebank' found") + val headerRow = sheet.getRow(0) ?: throw Exception("No header row found") + val headers = arrayOf( + "Index", + "Description", + "Day", + "Time", + "Soundpath", + "Repeat", + "Enable", + "BroadcastZones", + "Language" + ) + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found") + if (cell.stringCellValue != header) throw Exception("Header '$header' not found") + } + // clear existing schedulebank + Clear_Schedulebank() + // read each row and insert into database + val _schedulebankList = ArrayList() + for (rowIndex in 1..sheet.lastRowNum) { + val row = sheet.getRow(rowIndex) ?: continue + val description = row.getCell(1)?.stringCellValue ?: continue + val day = row.getCell(2)?.stringCellValue ?: continue + val time = row.getCell(3)?.stringCellValue ?: continue + val soundpath = row.getCell(4)?.stringCellValue ?: continue + val repeat = row.getCell(5)?.stringCellValue?.toUByteOrNull() ?: continue + val enable = row.getCell(6)?.stringCellValue?.toBooleanStrictOrNull() ?: continue + val broadcastZones = row.getCell(7)?.stringCellValue ?: continue + val language = row.getCell(8)?.stringCellValue ?: continue + val schedulebank = + ScheduleBank(0u, description, day, time, soundpath, repeat, enable, broadcastZones, language) + _schedulebankList.add(schedulebank) + } + return Add_Schedulebank(_schedulebankList) + } catch (e: Exception) { + Logger.error { "Error importing Schedulebank, Msg: ${e.message}" } + } + return false + } + /** * Reloads the language link list from the database. */ @@ -296,17 +568,97 @@ class MariaDB ( ) LanguageLinkList.add(languageLink) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error fetching language links: ${e.message}" as Any) } } + /** + * Adds a new language link entry to the database. + * @param languageLink The LanguageLink object to add. + * @return True if the addition was successful, false otherwise. + */ + fun Add_LanguageLink(languageLink: LanguageLink): Boolean { + try { + val statement = connection?.prepareStatement("INSERT INTO languagelink (TAG, Language) VALUES (?, ?)") + statement?.setString(1, languageLink.TAG) + statement?.setString(2, languageLink.Language) + val rowsAffected = statement?.executeUpdate() + if (rowsAffected != null && rowsAffected > 0) { + Logger.info("Language link added: ${languageLink.TAG} -> ${languageLink.Language}" as Any) + return true + } else { + Logger.warn("No language link entry added for: ${languageLink.TAG} -> ${languageLink.Language}" as Any) + } + } catch (e: Exception) { + Logger.error("Error adding language link entry: ${e.message}" as Any) + } + return false + } + + /** + * Adds multiple language link entries to the database using bulk insert. + * @param languageLinkList ArrayList of LanguageLink objects to add. + * @return True if the addition was successful, false otherwise. + */ + fun Add_LanguageLink(languageLinkList: ArrayList): Boolean { + if (languageLinkList.isNotEmpty()) { + try { + if (connection != null) { + connection!!.autoCommit = false + val sql = "INSERT INTO languagelink (TAG, Language) VALUES (?, ?)" + val statement = connection!!.prepareStatement(sql) + for (ll in languageLinkList) { + statement.setString(1, ll.TAG) + statement.setString(2, ll.Language) + statement.addBatch() + } + statement.executeBatch() + connection!!.commit() + Logger.info("Bulk languagelink insert successful: ${languageLinkList.size} entries" as Any) + connection!!.autoCommit = true + return true + } + } catch (e: Exception) { + Logger.error("Error adding languagelink entries: ${e.message}" as Any) + } + } + return false + } + + + /** + * Updates an existing language link entry in the database by its index. + * @param index The index of the language link entry to update. + * @param languageLink The LanguageLink object with updated values. + * @return True if the update was successful, false otherwise. + */ + fun Update_LanguageLink_by_index(index: UInt, languageLink: LanguageLink): Boolean { + try { + val statement = + connection?.prepareStatement("UPDATE languagelink SET TAG = ?, Language = ? WHERE `index` = ?") + statement?.setString(1, languageLink.TAG) + statement?.setString(2, languageLink.Language) + statement?.setLong(3, index.toLong()) + val rowsAffected = statement?.executeUpdate() + if (rowsAffected != null && rowsAffected > 0) { + Logger.info("Language link updated at index $index: ${languageLink.TAG} -> ${languageLink.Language}" as Any) + return true + } else { + Logger.warn("No language link entry updated at index $index for: ${languageLink.TAG} -> ${languageLink.Language}" as Any) + } + } catch (e: Exception) { + Logger.error("Error updating language link entry at index $index: ${e.message}" as Any) + } + return false + } + /** * Deletes a language link entry by its index. * @param index The index of the language link entry to delete. * @return True if the deletion was successful, false otherwise. */ - fun Delete_LanguageLink_by_index(index: UInt) : Boolean { + fun Delete_LanguageLink_by_index(index: UInt): Boolean { try { val statement = connection?.prepareStatement("DELETE FROM languagelink WHERE `index` = ?") statement?.setLong(1, index.toLong()) @@ -317,7 +669,7 @@ class MariaDB ( } else { Logger.warn("No rows deleted from languagelink with index $index" as Any) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error deleting from languagelink with index $index: ${e.message}" as Any) } return false @@ -326,7 +678,7 @@ class MariaDB ( /** * Clears all entries from the language link table in the database and the local list. */ - fun Clear_LanguageLink() : Boolean { + fun Clear_LanguageLink(): Boolean { try { val statement = connection?.createStatement() // use TRUNCATE to reset auto increment index @@ -334,12 +686,77 @@ class MariaDB ( Logger.info("LanguageLink table cleared" as Any) LanguageLinkList.clear() return true - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error clearing LanguageLink table: ${e.message}" as Any) } return false } + /** + * Exports the languagelink table to an XLSX workbook. + * @return An XSSFWorkbook containing the languagelink data, or null if an error occurred. + */ + fun Export_LanguageLink_XLSX(): XSSFWorkbook? { + try { + val statement = connection?.createStatement() + val resultSet = statement?.executeQuery("SELECT * FROM languagelink") + val workbook = XSSFWorkbook() + val sheet = workbook.createSheet("LanguageLink") + val headerRow = sheet.createRow(0) + val headers = arrayOf("Index", "TAG", "Language") + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.createCell(colIndex) + cell.setCellValue(header) + } + var rowIndex = 1 + while (resultSet?.next() == true) { + val row = sheet.createRow(rowIndex++) + row.createCell(0).setCellValue(resultSet.getString("index")) + row.createCell(1).setCellValue(resultSet.getString("TAG")) + row.createCell(2).setCellValue(resultSet.getString("Language")) + } + for (i in headers.indices) { + sheet.autoSizeColumn(i) + } + return workbook + } catch (e: Exception) { + Logger.error { "Error exporting LanguageLink, Msg: ${e.message}" } + } + return null + } + + /** + * Imports languagelink entries from an XLSX workbook. + * @param workbook The XSSFWorkbook containing the languagelink data. + * @return True if the import was successful, false otherwise. + */ + fun Import_LanguageLink_XLSX(workbook: XSSFWorkbook): Boolean { + try { + val sheet = workbook.getSheet("LanguageLink") ?: throw Exception("No sheet named 'LanguageLink' found") + val headerRow = sheet.getRow(0) ?: throw Exception("No header row found") + val headers = arrayOf("Index", "TAG", "Language") + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found") + if (cell.stringCellValue != header) throw Exception("Header '$header' not found") + } + // clear existing languagelink + Clear_LanguageLink() + // read each row and insert into database + val _languageLinkList = ArrayList() + for (rowIndex in 1..sheet.lastRowNum) { + val row = sheet.getRow(rowIndex) ?: continue + val tag = row.getCell(1)?.stringCellValue ?: continue + val language = row.getCell(2)?.stringCellValue ?: continue + val languageLink = LanguageLink(0u, tag, language) + _languageLinkList.add(languageLink) + } + return Add_LanguageLink(_languageLinkList) + } catch (e: Exception) { + Logger.error { "Error importing LanguageLink, Msg: ${e.message}" } + } + return false + } + /** * Reloads the soundbank list from the database. */ @@ -361,17 +778,137 @@ class MariaDB ( ) SoundbankList.add(soundbank) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error fetching soundbanks: ${e.message}" as Any) } } + /** + * Adds a new soundbank entry to the database. + * @param soundbank The Soundbank object to add. + * @return True if the addition was successful, false otherwise. + */ + fun Add_Soundbank(soundbank: Soundbank): Boolean { + try { + val statement = + connection?.prepareStatement("INSERT INTO soundbank (Description, TAG, Category, Language, VoiceType, Path) VALUES (?, ?, ?, ?, ?, ?)") + statement?.setString(1, soundbank.Description) + statement?.setString(2, soundbank.TAG) + statement?.setString(3, soundbank.Category) + statement?.setString(4, soundbank.Language) + statement?.setString(5, soundbank.VoiceType) + statement?.setString(6, soundbank.Path) + val rowsAffected = statement?.executeUpdate() + if (rowsAffected != null && rowsAffected > 0) { + Logger.info("Soundbank added: ${soundbank.Description}" as Any) + return true + } else { + Logger.warn("No soundbank entry added for: ${soundbank.Description}" as Any) + } + } catch (e: Exception) { + Logger.error("Error adding soundbank entry: ${e.message}" as Any) + } + return false + } + + /** + * Adds multiple soundbank entries to the database using bulk insert. + * @param soundbank Vararg of Soundbank objects to add. + * @return True if the addition was successful, false otherwise. + */ + fun Add_Soundbank(soundbank: ArrayList): Boolean { + if (soundbank.isNotEmpty()) { + // use mysql bulk insert + try { + if (connection != null) { + connection!!.autoCommit = false + val sql = + "INSERT INTO soundbank (Description, TAG, Category, Language, VoiceType, Path) VALUES (?, ?, ?, ?, ?, ?)" + val statement = connection!!.prepareStatement(sql) + for (sb in soundbank) { + statement.setString(1, sb.Description) + statement.setString(2, sb.TAG) + statement.setString(3, sb.Category) + statement.setString(4, sb.Language) + statement.setString(5, sb.VoiceType) + statement.setString(6, sb.Path) + statement.addBatch() + } + statement.executeBatch() + connection!!.commit() + Logger.info("Bulk soundbank insert successful: ${soundbank.size} entries" as Any) + connection!!.autoCommit = true + return true + } + } catch (e: Exception) { + Logger.error("Error adding soundbank entries: ${e.message}" as Any) + } + } + return false + } + + /** + * Updates an existing soundbank entry in the database by its index. + * @param index The index of the soundbank entry to update. + * @param soundbank The Soundbank object with updated values. + * @return True if the update was successful, false otherwise. + */ + fun Update_Soundbank_by_index(index: UInt, soundbank: Soundbank): Boolean { + try { + val statement = + connection?.prepareStatement("UPDATE soundbank SET Description = ?, TAG = ?, Category = ?, Language = ?, VoiceType = ?, Path = ? WHERE `index` = ?") + statement?.setString(1, soundbank.Description) + statement?.setString(2, soundbank.TAG) + statement?.setString(3, soundbank.Category) + statement?.setString(4, soundbank.Language) + statement?.setString(5, soundbank.VoiceType) + statement?.setString(6, soundbank.Path) + statement?.setLong(7, index.toLong()) + val rowsAffected = statement?.executeUpdate() + if (rowsAffected != null && rowsAffected > 0) { + Logger.info("Soundbank updated at index $index: ${soundbank.Description}" as Any) + return true + } else { + Logger.warn("No soundbank entry updated at index $index for: ${soundbank.Description}" as Any) + } + } catch (e: Exception) { + Logger.error("Error updating soundbank entry at index $index: ${e.message}" as Any) + } + return false + } + + /** + * Retrieves a soundbank entry by its index. + * @param index The index of the soundbank entry to retrieve. + * @return The Soundbank object if found, null otherwise. + */ + fun Get_Soundbank_by_index(index: UInt): Soundbank? { + try { + val statement = connection?.createStatement() + val resultSet = statement?.executeQuery("SELECT * FROM soundbank WHERE `index` = $index") + if (resultSet?.next() == true) { + return Soundbank( + resultSet.getLong("index").toUInt(), + resultSet.getString("Description"), + resultSet.getString("TAG"), + resultSet.getString("Category"), + resultSet.getString("Language"), + resultSet.getString("VoiceType"), + resultSet.getString("Path") + ) + } + } catch (_: Exception) { + Logger.error("Error finding soundbank with index $index" as Any) + } + return null + } + /** * Deletes a soundbank entry by its index. * @param index The index of the soundbank entry to delete. * @return True if the deletion was successful, false otherwise. */ - fun Delete_Soundbank_by_index(index: UInt) : Boolean { + fun Delete_Soundbank_by_index(index: UInt): Boolean { try { val statement = connection?.prepareStatement("DELETE FROM soundbank WHERE `index` = ?") statement?.setLong(1, index.toLong()) @@ -382,7 +919,7 @@ class MariaDB ( } else { Logger.warn("No rows deleted from soundbank with index $index" as Any) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error deleting from soundbank with index $index: ${e.message}" as Any) } return false @@ -391,7 +928,7 @@ class MariaDB ( /** * Clears all entries from the soundbank table in the database and the local list. */ - fun Clear_Soundbank() : Boolean{ + fun Clear_Soundbank(): Boolean { try { val statement = connection?.createStatement() // use TRUNCATE to reset auto increment index @@ -399,12 +936,82 @@ class MariaDB ( Logger.info("Soundbank table cleared" as Any) SoundbankList.clear() return true - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error clearing soundbank table: ${e.message}" as Any) } return false } + /** + * Exports the soundbank table to an XLSX workbook. + * @return An XSSFWorkbook containing the soundbank data, or null if an error occurred. + */ + fun Export_Soundbank_XLSX(): XSSFWorkbook? { + try { + val statement = connection?.createStatement() + val resultSet = statement?.executeQuery("SELECT * FROM soundbank") + val workbook = XSSFWorkbook() + val sheet = workbook.createSheet("Soundbank") + val headerRow = sheet.createRow(0) + val headers = arrayOf("Index", "Description", "TAG", "Category", "Language", "VoiceType", "Path") + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.createCell(colIndex) + cell.setCellValue(header) + } + var rowIndex = 1 + while (resultSet?.next() == true) { + val row = sheet.createRow(rowIndex++) + row.createCell(0).setCellValue(resultSet.getString("index")) + row.createCell(1).setCellValue(resultSet.getString("Description")) + row.createCell(2).setCellValue(resultSet.getString("TAG")) + row.createCell(3).setCellValue(resultSet.getString("Category")) + row.createCell(4).setCellValue(resultSet.getString("Language")) + row.createCell(5).setCellValue(resultSet.getString("VoiceType")) + row.createCell(6).setCellValue(resultSet.getString("Path")) + } + for (i in 0 until headers.size) { + sheet.autoSizeColumn(i) + } + return workbook + } catch (e: Exception) { + Logger.error { "Error exporting Soundbank, Msg: ${e.message}" } + } + return null + } + + fun Import_Soundbank_XLSX(workbook: XSSFWorkbook): Boolean { + try { + // check if there is sheet named "Soundbank" + val sheet = workbook.getSheet("Soundbank") ?: throw Exception("No sheet named 'Soundbank' found") + // check if the sheet contains header named "index", "Description", "TAG", "Category", "Language", "VoiceType", "Path" + val headerRow = sheet.getRow(0) ?: throw Exception("No header row found") + val headers = arrayOf("Index", "Description", "TAG", "Category", "Language", "VoiceType", "Path") + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found") + if (cell.stringCellValue != header) throw Exception("Header '$header' not found") + } + // clear existing soundbank + Clear_Soundbank() + // read each row and insert into database + val _soundbankList = ArrayList() + for (rowIndex in 1..sheet.lastRowNum) { + val row = sheet.getRow(rowIndex) ?: continue + val description = row.getCell(1)?.stringCellValue ?: continue + val tag = row.getCell(2)?.stringCellValue ?: continue + val category = row.getCell(3)?.stringCellValue ?: continue + val language = row.getCell(4)?.stringCellValue ?: continue + val voiceType = row.getCell(5)?.stringCellValue ?: continue + val path = row.getCell(6)?.stringCellValue ?: continue + val soundbank = Soundbank(0u, description, tag, category, language, voiceType, path) + _soundbankList.add(soundbank) + } + return Add_Soundbank(_soundbankList) + } catch (e: Exception) { + Logger.error { "Error importing Soundbank, Msg: ${e.message}" } + } + return false + } + /** * Reloads the messagebank list from the database. */ @@ -426,17 +1033,110 @@ class MariaDB ( MessagebankList.add(messagebank) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error fetching messagebanks: ${e.message}" as Any) } } + /** + * Adds a new messagebank entry to the database. + * @param messagebank The Messagebank object to add. + * @return True if the addition was successful, false otherwise. + */ + fun Add_Messagebank(messagebank: Messagebank): Boolean { + try { + val statement = + connection?.prepareStatement("INSERT INTO messagebank (Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS) VALUES (?, ?, ?, ?, ?, ?)") + statement?.setString(1, messagebank.Description) + statement?.setString(2, messagebank.Language) + statement?.setInt(3, messagebank.ANN_ID.toInt()) + statement?.setString(4, messagebank.Voice_Type) + statement?.setString(5, messagebank.Message_Detail) + statement?.setString(6, messagebank.Message_TAGS) + val rowsAffected = statement?.executeUpdate() + if (rowsAffected != null && rowsAffected > 0) { + Logger.info("Messagebank added: ${messagebank.Description}" as Any) + return true + } else { + Logger.warn("No messagebank entry added for: ${messagebank.Description}" as Any) + } + } catch (e: Exception) { + Logger.error("Error adding messagebank entry: ${e.message}" as Any) + } + return false + } + + /** + * Adds multiple messagebank entries to the database using bulk insert. + * @param messagebankList ArrayList of Messagebank objects to add. + * @return True if the addition was successful, false otherwise. + */ + fun Add_Messagebank(messagebankList: ArrayList): Boolean { + if (messagebankList.isNotEmpty()) { + try { + if (connection != null) { + connection!!.autoCommit = false + val sql = + "INSERT INTO messagebank (Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS) VALUES (?, ?, ?, ?, ?, ?)" + val statement = connection!!.prepareStatement(sql) + for (mb in messagebankList) { + statement.setString(1, mb.Description) + statement.setString(2, mb.Language) + statement.setInt(3, mb.ANN_ID.toInt()) + statement.setString(4, mb.Voice_Type) + statement.setString(5, mb.Message_Detail) + statement.setString(6, mb.Message_TAGS) + statement.addBatch() + } + statement.executeBatch() + connection!!.commit() + Logger.info("Bulk messagebank insert successful: ${messagebankList.size} entries" as Any) + connection!!.autoCommit = true + return true + } + } catch (e: Exception) { + Logger.error("Error adding messagebank entries: ${e.message}" as Any) + } + } + return false + } + + /** + * Updates an existing messagebank entry in the database by its index. + * @param index The index of the messagebank entry to update. + * @param messagebank The Messagebank object with updated values. + * @return True if the update was successful, false otherwise. + */ + fun Update_Messagebank_by_index(index: UInt, messagebank: Messagebank): Boolean { + try { + val statement = + connection?.prepareStatement("UPDATE messagebank SET Description = ?, Language = ?, ANN_ID = ?, Voice_Type = ?, Message_Detail = ?, Message_TAGS = ? WHERE `index` = ?") + statement?.setString(1, messagebank.Description) + statement?.setString(2, messagebank.Language) + statement?.setInt(3, messagebank.ANN_ID.toInt()) + statement?.setString(4, messagebank.Voice_Type) + statement?.setString(5, messagebank.Message_Detail) + statement?.setString(6, messagebank.Message_TAGS) + statement?.setLong(7, index.toLong()) + val rowsAffected = statement?.executeUpdate() + if (rowsAffected != null && rowsAffected > 0) { + Logger.info("Messagebank updated at index $index: ${messagebank.Description}" as Any) + return true + } else { + Logger.warn("No messagebank entry updated at index $index for: ${messagebank.Description}" as Any) + } + } catch (e: Exception) { + Logger.error("Error updating messagebank entry at index $index: ${e.message}" as Any) + } + return false + } + /** * Deletes a messagebank entry by its index. * @param index The index of the messagebank entry to delete. * @return True if the deletion was successful, false otherwise. */ - fun Delete_Messagebank_by_index(index: UInt) : Boolean { + fun Delete_Messagebank_by_index(index: UInt): Boolean { try { val statement = connection?.prepareStatement("DELETE FROM messagebank WHERE `index` = ?") statement?.setLong(1, index.toLong()) @@ -447,16 +1147,89 @@ class MariaDB ( } else { Logger.warn("No rows deleted from messagebank with index $index" as Any) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error deleting from messagebank with index $index: ${e.message}" as Any) } return false } + /** + * Exports the messagebank table to an XLSX workbook. + * @return An XSSFWorkbook containing the messagebank data. + */ + fun Export_Messagebank_XLSX(): XSSFWorkbook { + try { + val statement = connection?.createStatement() + val resultSet = statement?.executeQuery("SELECT * FROM messagebank") + val workbook = XSSFWorkbook() + val sheet = workbook.createSheet("Messagebank") + val headerRow = sheet.createRow(0) + val headers = + arrayOf("Index", "Description", "Language", "ANN_ID", "Voice_Type", "Message_Detail", "Message_TAGS") + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.createCell(colIndex) + cell.setCellValue(header) + } + var rowIndex = 1 + while (resultSet?.next() == true) { + val row = sheet.createRow(rowIndex++) + row.createCell(0).setCellValue(resultSet.getString("index")) + row.createCell(1).setCellValue(resultSet.getString("Description")) + row.createCell(2).setCellValue(resultSet.getString("Language")) + row.createCell(3).setCellValue(resultSet.getString("ANN_ID")) + row.createCell(4).setCellValue(resultSet.getString("Voice_Type")) + row.createCell(5).setCellValue(resultSet.getString("Message_Detail")) + row.createCell(6).setCellValue(resultSet.getString("Message_TAGS")) + } + for (i in headers.indices) { + sheet.autoSizeColumn(i) + } + return workbook + } catch (e: Exception) { + Logger.error { "Error exporting Messagebank, Msg: ${e.message}" } + } + return XSSFWorkbook() + } + + fun Import_Messagebank_XLSX(workbook: XSSFWorkbook): Boolean { + try { + // check if there is sheet named "Messagebank" + val sheet = workbook.getSheet("Messagebank") ?: throw Exception("No sheet named 'Messagebank' found") + // check if the sheet contains header named "Index", "Description", "Language", "ANN_ID", "Voice_Type", "Message_Detail", "Message_TAGS" + val headerRow = sheet.getRow(0) ?: throw Exception("No header row found") + val headers = + arrayOf("Index", "Description", "Language", "ANN_ID", "Voice_Type", "Message_Detail", "Message_TAGS") + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found") + if (cell.stringCellValue != header) throw Exception("Header '$header' not found") + } + // clear existing messagebank + Clear_Messagebank() + // read each row and insert into database + val _messagebankList = ArrayList() + for (rowIndex in 1..sheet.lastRowNum) { + val row = sheet.getRow(rowIndex) ?: continue + val description = row.getCell(1)?.stringCellValue ?: continue + val language = row.getCell(2)?.stringCellValue ?: continue + val annId = row.getCell(3)?.stringCellValue?.toUIntOrNull() ?: continue + val voiceType = row.getCell(4)?.stringCellValue ?: continue + val messageDetail = row.getCell(5)?.stringCellValue ?: continue + val messageTags = row.getCell(6)?.stringCellValue ?: continue + val messagebank = Messagebank(0u, description, language, annId, voiceType, messageDetail, messageTags) + _messagebankList.add(messagebank) + } + return Add_Messagebank(_messagebankList) + } catch (e: Exception) { + Logger.error { "Error importing Messagebank, Msg: ${e.message}" } + } + return false + } + + /** * Clears all entries from the messagebank table in the database and the local list. */ - fun Clear_Messagebank() : Boolean{ + fun Clear_Messagebank(): Boolean { try { val statement = connection?.createStatement() // use TRUNCATE to reset auto increment index @@ -464,7 +1237,7 @@ class MariaDB ( Logger.info("Messagebank table cleared" as Any) MessagebankList.clear() return true - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error clearing messagebank table: ${e.message}" as Any) } return false @@ -474,7 +1247,7 @@ class MariaDB ( * Reads all entries from the queue_table in the database. * @return A list of QueueTable entries. */ - fun Read_Queue_Table() : List { + fun Read_Queue_Table(): List { val queueList = ArrayList() try { val statement = connection?.createStatement() @@ -493,7 +1266,7 @@ class MariaDB ( ) queueList.add(queueTable) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error fetching queue table: ${e.message}" as Any) } return queueList @@ -504,7 +1277,7 @@ class MariaDB ( * @param index The index of the queue_table entry to delete. * @return True if the deletion was successful, false otherwise. */ - fun Delete_Queue_Table_by_index(index: UInt) : Boolean { + fun Delete_Queue_Table_by_index(index: UInt): Boolean { try { val statement = connection?.prepareStatement("DELETE FROM queue_table WHERE `index` = ?") statement?.setLong(1, index.toLong()) @@ -515,7 +1288,7 @@ class MariaDB ( } else { Logger.warn("No rows deleted from queue_table with index $index" as Any) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error deleting from queue_table with index $index: ${e.message}" as Any) } return false @@ -524,14 +1297,14 @@ class MariaDB ( /** * Clears all entries from the queue_table in the database. */ - fun Clear_Queue_Table() : Boolean { + fun Clear_Queue_Table(): Boolean { try { val statement = connection?.createStatement() // use TRUNCATE to reset auto increment index statement?.executeUpdate("TRUNCATE TABLE queue_table") Logger.info("Queue table cleared" as Any) return true - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error clearing queue table: ${e.message}" as Any) } return false @@ -541,7 +1314,7 @@ class MariaDB ( * Reads all entries from the queue_paging in the database. * @return A list of QueuePaging entries. */ - fun Read_Queue_Paging() : List{ + fun Read_Queue_Paging(): List { val queueList = ArrayList() try { val statement = connection?.createStatement() @@ -557,7 +1330,7 @@ class MariaDB ( ) queueList.add(queuePaging) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error fetching queue paging: ${e.message}" as Any) } return queueList @@ -568,7 +1341,7 @@ class MariaDB ( * @param index The index of the queue_paging entry to delete. * @return True if the deletion was successful, false otherwise. */ - fun Delete_Queue_Paging_by_index(index: UInt) : Boolean { + fun Delete_Queue_Paging_by_index(index: UInt): Boolean { try { val statement = connection?.prepareStatement("DELETE FROM queue_paging WHERE `index` = ?") statement?.setLong(1, index.toLong()) @@ -579,7 +1352,7 @@ class MariaDB ( } else { Logger.warn("No rows deleted from queue_paging with index $index" as Any) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error deleting from queue_paging with index $index: ${e.message}" as Any) } return false @@ -588,14 +1361,14 @@ class MariaDB ( /** * Clears all entries from the queue_paging in the database. */ - fun Clear_Queue_Paging() : Boolean{ + fun Clear_Queue_Paging(): Boolean { try { val statement = connection?.createStatement() // use TRUNCATE to reset auto increment index statement?.executeUpdate("TRUNCATE TABLE queue_paging") Logger.info("Queue paging table cleared" as Any) return true - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error clearing queue paging table: ${e.message}" as Any) } return false @@ -620,18 +1393,105 @@ class MariaDB ( ) zonesList.add(zone) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error fetching broadcast zones: ${e.message}" as Any) } return zonesList } + /** + * Adds a new broadcast_zones entry to the database. + * @param broadcastZones The BroadcastZones object to add. + * @return True if the addition was successful, false otherwise. + */ + fun Add_BroadcastZones(broadcastZones: BroadcastZones): Boolean { + try { + val statement = + connection?.prepareStatement("INSERT INTO broadcast_zones (description, SoundChannel, Box, Relay) VALUES (?, ?, ?, ?)") + statement?.setString(1, broadcastZones.description) + statement?.setString(2, broadcastZones.SoundChannel) + statement?.setString(3, broadcastZones.Box) + statement?.setString(4, broadcastZones.Relay) + val rowsAffected = statement?.executeUpdate() + if (rowsAffected != null && rowsAffected > 0) { + Logger.info("Broadcast zone added: ${broadcastZones.description}" as Any) + return true + } else { + Logger.warn("No broadcast zone entry added for: ${broadcastZones.description}" as Any) + } + } catch (e: Exception) { + Logger.error("Error adding broadcast zone entry: ${e.message}" as Any) + } + return false + } + + /** + * Adds multiple broadcast_zones entries to the database using bulk insert. + * @param broadcastZonesList ArrayList of BroadcastZones objects to add. + * @return True if the addition was successful, false otherwise. + */ + fun Add_BroadcastZones(broadcastZonesList: ArrayList): Boolean { + if (broadcastZonesList.isNotEmpty()) { + try { + if (connection != null) { + connection!!.autoCommit = false + val sql = + "INSERT INTO broadcast_zones (description, SoundChannel, Box, Relay) VALUES (?, ?, ?, ?)" + val statement = connection!!.prepareStatement(sql) + for (bz in broadcastZonesList) { + statement.setString(1, bz.description) + statement.setString(2, bz.SoundChannel) + statement.setString(3, bz.Box) + statement.setString(4, bz.Relay) + statement.addBatch() + } + statement.executeBatch() + connection!!.commit() + Logger.info("Bulk broadcast_zones insert successful: ${broadcastZonesList.size} entries" as Any) + connection!!.autoCommit = true + return true + } + } catch (e: Exception) { + Logger.error("Error adding broadcast_zones entries: ${e.message}" as Any) + } + } + return false + } + + /** + * Updates an existing broadcast_zones entry in the database by its index. + * @param index The index of the broadcast_zones entry to update. + * @param broadcastZones The BroadcastZones object with updated values. + * @return True if the update was successful, false otherwise. + */ + fun Update_BroadcastZones_by_index(index: UInt, broadcastZones: BroadcastZones): Boolean { + try { + val statement = + connection?.prepareStatement("UPDATE broadcast_zones SET description = ?, SoundChannel = ?, Box = ?, Relay = ? WHERE `index` = ?") + statement?.setString(1, broadcastZones.description) + statement?.setString(2, broadcastZones.SoundChannel) + statement?.setString(3, broadcastZones.Box) + statement?.setString(4, broadcastZones.Relay) + statement?.setLong(5, index.toLong()) + val rowsAffected = statement?.executeUpdate() + if (rowsAffected != null && rowsAffected > 0) { + Logger.info("Broadcast zone updated at index $index: ${broadcastZones.description}" as Any) + return true + } else { + Logger.warn("No broadcast zone entry updated at index $index for: ${broadcastZones.description}" as Any) + } + } catch (e: Exception) { + Logger.error("Error updating broadcast zone entry at index $index: ${e.message}" as Any) + } + return false + } + /** * Deletes a broadcast_zones entry by its index. * @param index The index of the broadcast_zones entry to delete. * @return True if the deletion was successful, false otherwise. */ - fun Delete_BroadcastZones_by_index(index: UInt) : Boolean { + fun Delete_BroadcastZones_by_index(index: UInt): Boolean { try { val statement = connection?.prepareStatement("DELETE FROM broadcast_zones WHERE `index` = ?") statement?.setLong(1, index.toLong()) @@ -642,7 +1502,7 @@ class MariaDB ( } else { Logger.warn("No rows deleted from broadcast_zones with index $index" as Any) } - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error deleting from broadcast_zones with index $index: ${e.message}" as Any) } return false @@ -651,17 +1511,86 @@ class MariaDB ( /** * Clears all entries from the broadcast_zones in the database. */ - fun Clear_BroadcastZones() : Boolean { + fun Clear_BroadcastZones(): Boolean { try { val statement = connection?.createStatement() // use TRUNCATE to reset auto increment index statement?.executeUpdate("TRUNCATE TABLE broadcast_zones") Logger.info("Broadcast zones table cleared" as Any) return true - } catch (e : Exception) { + } catch (e: Exception) { Logger.error("Error clearing broadcast zones table: ${e.message}" as Any) } return false } + /** + * Exports the broadcast_zones table to an XLSX workbook. + * @return An XSSFWorkbook containing the broadcast_zones data, or null if an error occurred. + */ + fun Export_BroadcastZones_XLSX(): XSSFWorkbook? { + try { + val statement = connection?.createStatement() + val resultSet = statement?.executeQuery("SELECT * FROM broadcast_zones") + val workbook = XSSFWorkbook() + val sheet = workbook.createSheet("BroadcastZones") + val headerRow = sheet.createRow(0) + val headers = arrayOf("Index", "description", "SoundChannel", "Box", "Relay") + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.createCell(colIndex) + cell.setCellValue(header) + } + var rowIndex = 1 + while (resultSet?.next() == true) { + val row = sheet.createRow(rowIndex++) + row.createCell(0).setCellValue(resultSet.getString("index")) + row.createCell(1).setCellValue(resultSet.getString("description")) + row.createCell(2).setCellValue(resultSet.getString("SoundChannel")) + row.createCell(3).setCellValue(resultSet.getString("Box")) + row.createCell(4).setCellValue(resultSet.getString("Relay")) + } + for (i in headers.indices) { + sheet.autoSizeColumn(i) + } + return workbook + } catch (e: Exception) { + Logger.error { "Error exporting BroadcastZones, Msg: ${e.message}" } + } + return null + } + + /** + * Imports broadcast_zones entries from an XLSX workbook. + * @param workbook The XSSFWorkbook containing the broadcast_zones data. + * @return True if the import was successful, false otherwise. + */ + fun Import_BroadcastZones_XLSX(workbook: XSSFWorkbook): Boolean { + try { + val sheet = workbook.getSheet("BroadcastZones") ?: throw Exception("No sheet named 'BroadcastZones' found") + val headerRow = sheet.getRow(0) ?: throw Exception("No header row found") + val headers = arrayOf("Index", "description", "SoundChannel", "Box", "Relay") + for ((colIndex, header) in headers.withIndex()) { + val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found") + if (cell.stringCellValue != header) throw Exception("Header '$header' not found") + } + // clear existing broadcast_zones + Clear_BroadcastZones() + // read each row and insert into database + val _broadcastZonesList = ArrayList() + for (rowIndex in 1..sheet.lastRowNum) { + val row = sheet.getRow(rowIndex) ?: continue + val description = row.getCell(1)?.stringCellValue ?: continue + val soundChannel = row.getCell(2)?.stringCellValue ?: continue + val box = row.getCell(3)?.stringCellValue ?: continue + val relay = row.getCell(4)?.stringCellValue ?: continue + val broadcastZone = BroadcastZones(0u, description, soundChannel, box, relay) + _broadcastZonesList.add(broadcastZone) + } + return Add_BroadcastZones(_broadcastZonesList) + } catch (e: Exception) { + Logger.error { "Error importing BroadcastZones, Msg: ${e.message}" } + } + return false + } + } \ No newline at end of file diff --git a/src/database/Messagebank.kt b/src/database/Messagebank.kt index 8482e8c..e3d5fbf 100644 --- a/src/database/Messagebank.kt +++ b/src/database/Messagebank.kt @@ -1,11 +1,11 @@ package database data class Messagebank( - val index : UInt, - val Description: String, - val Language: String, - val ANN_ID : UInt, - val Voice_Type: String, - val Message_Detail: String, - val Message_TAGS: String + var index : UInt, + var Description: String, + var Language: String, + var ANN_ID : UInt, + var Voice_Type: String, + var Message_Detail: String, + var Message_TAGS: String ) diff --git a/src/database/QueueFids.kt b/src/database/QueueFids.kt index b8642f1..5711b6c 100644 --- a/src/database/QueueFids.kt +++ b/src/database/QueueFids.kt @@ -1,4 +1,4 @@ package database @Suppress("unused") -data class QueueFids(val index: UInt, val ALCODE: String, val FLNUM: String, val ORIGIN: String, val ETAD: String, val FREMARK: String) +data class QueueFids(var index: UInt, var ALCODE: String, var FLNUM: String, var ORIGIN: String, var ETAD: String, var FREMARK: String) diff --git a/src/database/QueuePaging.kt b/src/database/QueuePaging.kt index 18857b0..ae6bb26 100644 --- a/src/database/QueuePaging.kt +++ b/src/database/QueuePaging.kt @@ -1,4 +1,4 @@ package database @Suppress("unused") -data class QueuePaging(val index: UInt, val Date_Time: String, val Source: String, val Type: String, val Message: String, val BroadcastZones: String) +data class QueuePaging(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var BroadcastZones: String) diff --git a/src/database/QueueTable.kt b/src/database/QueueTable.kt index 07f55d7..3c22588 100644 --- a/src/database/QueueTable.kt +++ b/src/database/QueueTable.kt @@ -1,4 +1,4 @@ package database @Suppress("unused") -data class QueueTable(val index: UInt, val Date_Time: String, val Source: String, val Type: String, val Message: String, val SB_TAGS: String, val BroadcastZones: String, val Repeat: UInt, val Language: String) +data class QueueTable(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var SB_TAGS: String, var BroadcastZones: String, var Repeat: UInt, var Language: String) diff --git a/src/database/ScheduleBank.kt b/src/database/ScheduleBank.kt index 99d1269..01b795e 100644 --- a/src/database/ScheduleBank.kt +++ b/src/database/ScheduleBank.kt @@ -1,4 +1,4 @@ package database @Suppress("unused") -data class ScheduleBank(val index: UInt, val Description: String, val Day: String, val Time: String, val Soundpath: String, val Repeat: UByte, val Enable: Boolean, val BroadcastZones: String, val 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) diff --git a/src/database/Soundbank.kt b/src/database/Soundbank.kt index 5ccb3f1..00c18b7 100644 --- a/src/database/Soundbank.kt +++ b/src/database/Soundbank.kt @@ -1,11 +1,11 @@ package database data class Soundbank( - val index: UInt, - val Description: String, - val TAG : String, - val Category: String, - val Language: String, - val VoiceType: String, - val Path: String, + var index: UInt, + var Description: String, + var TAG : String, + var Category: String, + var Language: String, + var VoiceType: String, + var Path: String, ) diff --git a/src/database/UserDB.kt b/src/database/UserDB.kt index 6325aef..45c8e21 100644 --- a/src/database/UserDB.kt +++ b/src/database/UserDB.kt @@ -1,4 +1,4 @@ package database @Suppress("unused") -data class UserDB(val index: UInt, val username: String, val password: String, val location: String) +data class UserDB(var index: UInt, var username: String, var password: String, var location: String) diff --git a/src/web/WebApp.kt b/src/web/WebApp.kt index 7e0704d..541c1d0 100644 --- a/src/web/WebApp.kt +++ b/src/web/WebApp.kt @@ -2,17 +2,23 @@ package web import codes.Somecodes import codes.Somecodes.Companion.ValidDate +import codes.Somecodes.Companion.ValidFile import codes.Somecodes.Companion.ValidString +import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import content.Category import database.MariaDB +import database.Soundbank 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.http.Context +import io.javalin.json.JavalinJackson import io.javalin.websocket.WsMessageContext import java.time.LocalDateTime @@ -36,6 +42,7 @@ class WebApp(val listenPort: Int, val userlist: List>, val config -> config.useVirtualThreads = true config.staticFiles.add("/webpage") + config.jsonMapper(JavalinJackson(jacksonObjectMapper())) config.router.apiBuilder { path("/"){ get { ctx -> @@ -133,143 +140,250 @@ class WebApp(val listenPort: Int, val userlist: List>, val } path("soundbank.html") { before {CheckUsers(it)} - get("List"){ - // get soundbank list - it.result(MariaDB.ArrayListtoString(db.SoundbankList)) - } - delete("List"){ - // truncate soundbank table - if (db.Clear_Soundbank()){ - it.result("OK") - } else { - it.status(500).result("Failed to truncate soundbank table") - } - } - delete("DeleteByIndex/{index}"){ - // delete by index - val index = it.pathParam("index").toUIntOrNull() - if (index == null){ - it.status(400).result("Invalid index") - } else{ - if (db.Delete_Soundbank_by_index(index)){ - it.result("OK") - } else { - it.status(500).result("Failed to delete soundbank with index $index") - } - } - - } - - - } path("messagebank.html") { before { CheckUsers(it) } - get("List"){ - // get messagebank list - it.result(MariaDB.ArrayListtoString(db.MessagebankList)) - } - delete("List"){ - // truncate messagebank table - if (db.Clear_Messagebank()){ - it.result("OK") - } else { - it.status(500).result("Failed to truncate messagebank table") - } - } - delete("DeleteByIndex/{index}"){ - // delete by index - val index = it.pathParam("index").toUIntOrNull() - if (index == null){ - it.status(400).result("Invalid index") - } else{ - if (db.Delete_Messagebank_by_index(index)){ - it.result("OK") - } else { - it.status(500).result("Failed to delete messagebank with index $index") - } - } - } } path("language.html") { before { CheckUsers(it) } - get("List"){ - // get language link list - it.result(MariaDB.ArrayListtoString(db.LanguageLinkList)) - } - delete("List"){ - // truncate language link table - if (db.Clear_LanguageLink()){ - it.result("OK") - } else { - it.status(500).result("Failed to truncate language link table") - } - } - delete("DeleteByIndex/{index}"){ - // delete by index - val index = it.pathParam("index").toUIntOrNull() - if (index == null){ - it.status(400).result("Invalid index") - } else{ - if (db.Delete_LanguageLink_by_index(index)){ - it.result("OK") - } else { - it.status(500).result("Failed to delete language link with index $index") - } - } - } - } path("log.html") { before { CheckUsers(it) } - get("List//"){ get1 -> - val logdate = get1.pathParam("logdate") - val logfilter = get1.pathParam("logfilter") - if (ValidDate(logdate)){ - if (ValidString(logfilter)){ - // ada log filter - db.GetLog(logdate, logfilter){ - get1.result(MariaDB.ArrayListtoString(it)) - } - } else { - db.GetLog(logdate){ - get1.result(MariaDB.ArrayListtoString(it)) - } - } - } else get1.status(400).result("Invalid logdate") - } - } path("setting.html") { before { CheckUsers(it) } - } path("timer.html") { before { CheckUsers(it) } - get("List"){ - // get timer list - it.result(MariaDB.ArrayListtoString(db.SchedulebankList)) - } - delete("List"){ - // truncate timer table - if (db.Clear_Schedulebank()){ - it.result("OK") - } else { - it.status(500).result("Failed to truncate schedulebank table") + } + path("api"){ + path("SoundBank"){ + get("List"){ + // get soundbank list + it.result(MariaDB.ArrayListtoString(db.SoundbankList)) } - } - delete("DeleteByIndex/{index}"){ - // delete by index - val index = it.pathParam("index").toUIntOrNull() - if (index == null){ - it.status(400).result("Invalid index") - } else{ - if (db.Delete_Schedulebank_by_index(index)){ + post("Add"){ + try { + val addvalue = objectmapper.readValue(it.body(), Soundbank::class.java) + if (ValidString(addvalue.Description)){ + if (ValidString(addvalue.TAG)){ + if (ValidString(addvalue.Category)){ + if (ValidString(addvalue.Language)){ + if (ValidString(addvalue.Path)){ + // check apakah TAG sudah ada untuk language dan category yang sama + val exists = db.SoundbankList.any { sb -> + sb.TAG == addvalue.TAG && sb.Language == addvalue.Language && sb.Category == addvalue.Category + } + if (!exists){ + if (ValidFile(addvalue.Path)){ + if (db.Add_Soundbank(addvalue)){ + it.result("OK") + } else it.status(500).result("Failed to add soundbank to database") + } 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 Language") + } else it.status(400).result("Invalid Category") + } else it.status(400).result("Invalid TAG") + } else it.status(400).result("Invalid Description") + } catch (_: Exception){ + it.status(400).result("Invalid request body") + } + } + delete("List"){ + // truncate soundbank table + if (db.Clear_Soundbank()){ it.result("OK") } else { - it.status(500).result("Failed to delete schedule with index $index") + it.status(500).result("Failed to truncate soundbank table") } } + delete("DeleteByIndex/{index}"){ + // delete by index + val index = it.pathParam("index").toUIntOrNull() + if (index == null){ + it.status(400).result("Invalid index") + } else{ + if (db.Delete_Soundbank_by_index(index)){ + it.result("OK") + } else { + it.status(500).result("Failed to delete soundbank with index $index") + } + } + } + patch("UpdateByIndex/{index}"){ + // update by index + val index = it.pathParam("index").toUIntOrNull() + if (index == null){ + // tidak ada path param index + it.status(400).result("Invalid index") + } else { + val sb = db.SoundbankList.find{ xx -> xx.index == index } + if (sb == null){ + // soundbank dengan index tersebut tidak ditemukan + it.status(404).result("Soundbank with index $index not found") + } else { + // soundbank dengan index tersebut ditemukan, sekarang update + 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 _tag = json.get("TAG").asText() + val _category = json.get("Category").asText() + val _language = json.get("Language").asText() + val _path = json.get("Path").asText() + var changed = false + if (ValidString(_description) && _description!=sb.Description){ + sb.Description = _description + changed = true + } + if (ValidString(_tag) && _tag!=sb.TAG){ + sb.TAG = _tag + changed = true + } + if (ValidString(_category) && _category!=sb.Category){ + if (Category.entries.any { + cat -> cat.name == _category + }) { + sb.Category = _category + changed = true + } else { + it.status(400).result("Invalid Category") + return@patch + } + } + if (ValidString(_language) && _language!=sb.Language){ + sb.Language = _language + changed = true + } + if (ValidString(_path) && _path!=sb.Path){ + if (ValidFile(_path)){ + sb.Path = _path + changed = true + } else { + it.status(400).result("Invalid Path, file does not exist") + return@patch + } + } + if (changed){ + if (db.Update_Soundbank_by_index(index, sb)){ + it.result("OK") + } 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") + } + } + } + } + get("ExportXLSX"){ + + } + post("ImportXLSX"){ + val uploaded = it.uploadedFile("file") + if (uploaded==null){ + it.status(400).result("No file uploaded") + return@post + } + } + } + path("MessageBank"){ + get("List"){ + // get messagebank list + it.result(MariaDB.ArrayListtoString(db.MessagebankList)) + } + delete("List"){ + // truncate messagebank table + if (db.Clear_Messagebank()){ + it.result("OK") + } else { + it.status(500).result("Failed to truncate messagebank table") + } + } + delete("DeleteByIndex/{index}"){ + // delete by index + val index = it.pathParam("index").toUIntOrNull() + if (index == null){ + it.status(400).result("Invalid index") + } else{ + if (db.Delete_Messagebank_by_index(index)){ + it.result("OK") + } else { + it.status(500).result("Failed to delete messagebank with index $index") + } + } + } + } + path("LanguageLink"){ + get("List"){ + // get language link list + it.result(MariaDB.ArrayListtoString(db.LanguageLinkList)) + } + delete("List"){ + // truncate language link table + if (db.Clear_LanguageLink()){ + it.result("OK") + } else { + it.status(500).result("Failed to truncate language link table") + } + } + delete("DeleteByIndex/{index}"){ + // delete by index + val index = it.pathParam("index").toUIntOrNull() + if (index == null){ + it.status(400).result("Invalid index") + } else{ + if (db.Delete_LanguageLink_by_index(index)){ + it.result("OK") + } else { + it.status(500).result("Failed to delete language link with index $index") + } + } + } + } + path("ScheduleBank"){ + get("List"){ + // get timer list + it.result(MariaDB.ArrayListtoString(db.SchedulebankList)) + } + delete("List"){ + // truncate timer table + if (db.Clear_Schedulebank()){ + it.result("OK") + } else { + it.status(500).result("Failed to truncate schedulebank table") + } + } + delete("DeleteByIndex/{index}"){ + // delete by index + val index = it.pathParam("index").toUIntOrNull() + if (index == null){ + it.status(400).result("Invalid index") + } else{ + if (db.Delete_Schedulebank_by_index(index)){ + it.result("OK") + } else { + it.status(500).result("Failed to delete schedule with index $index") + } + } + } + } + path("Log"){ + get("List//"){ get1 -> + val logdate = get1.pathParam("logdate") + val logfilter = get1.pathParam("logfilter") + if (ValidDate(logdate)){ + if (ValidString(logfilter)){ + // ada log filter + db.GetLog(logdate, logfilter){ + get1.result(MariaDB.ArrayListtoString(it)) + } + } else { + db.GetLog(logdate){ + get1.result(MariaDB.ArrayListtoString(it)) + } + } + } else get1.status(400).result("Invalid logdate") + } } } }