package codes import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import content.Category import content.Language import content.NetworkInformation import content.ScheduleDay import content.VoiceType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.tinylog.Logger import oshi.SystemInfo import oshi.hardware.CentralProcessor import oshi.hardware.GlobalMemory import oshi.hardware.NetworkIF import oshi.hardware.Sensors import oshi.software.os.OperatingSystem import java.nio.file.Files import java.nio.file.Path import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.function.Consumer import kotlin.io.path.name @Suppress("unused") class Somecodes { companion object { val current_directory : String = System.getProperty("user.dir") var Soundbank_directory : Path = Path.of(current_directory,"Soundbank") val SoundbankResult_directory : Path = Path.of(current_directory,"SoundbankResult") val PagingResult_directory : Path = Path.of(current_directory,"PagingResult") val si = SystemInfo() val processor: CentralProcessor = si.hardware.processor val memory : GlobalMemory = si.hardware.memory val NetworkInfoMap = mutableMapOf() val sensor : Sensors = si.hardware.sensors val os : OperatingSystem = si.operatingSystem val datetimeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss") val dateformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy") val dateformat2: DateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy") val timeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm:ss") val timeformat2: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm") val filenameformat: DateTimeFormatter = DateTimeFormatter.ofPattern("ddMMyyyy_HHmmss") const val KB_threshold = 1024.0 const val MB_threshold = KB_threshold * 1024.0 const val GB_threshold = MB_threshold * 1024.0 const val TB_threshold = GB_threshold * 1024.0 val objectmapper = jacksonObjectMapper() // regex for getting ann_id from Message, which is the number inside [] private val ann_id_regex = Regex("\\[(\\d+)]") /** * Get the directory path for a specific language, voice type, and category. * @param language The language. * @param voice The voice type. * @param category The category. * @return The path to the directory. */ fun SoundbankDirectory(language: Language, voice: VoiceType, category: Category) : Path{ return Soundbank_directory.resolve(language.name).resolve(voice.name).resolve(category.name) } fun SoundbankDirectory(language: String, voice: String, category: String) : Path{ return SoundbankDirectory( Language.valueOf(language), VoiceType.valueOf(voice), Category.valueOf(category) ) } fun ExtractFilesFromClassPath(resourcePath: String, outputDir: Path) { try { val resource = Somecodes::class.java.getResource(resourcePath) if (resource != null) { val uri = resource.toURI() val path = if (uri.scheme == "jar") { val fileSystem = java.nio.file.FileSystems.newFileSystem(uri, emptyMap()) fileSystem.getPath(resourcePath) } else { Path.of(uri) } Files.walk(path).use { stream -> stream.forEach { sourcePath -> if (Files.isRegularFile(sourcePath)) { val fn = sourcePath.fileName val targetPath = outputDir.resolve(fn) if (Files.isRegularFile(targetPath) && Files.size(targetPath) > 0) { Logger.info { "File $targetPath already exists, skipping extraction." } return@forEach } Files.copy(sourcePath, targetPath) Logger.info { "Extracted ${sourcePath.name} to $targetPath" } return@forEach } } } } else Logger.error { "Resource $resource not found" } } catch (e: Exception) { Logger.error { "Exception while extracting $resourcePath, Message = ${e.message}"} } } /** * Check if a string is a valid number. */ fun IsNumber(value: String) : Boolean { return value.toIntOrNull() != null } /** * Check if a string is alphabetic (contains only letters). */ fun IsAlphabethic(value: String) : Boolean { return value.all { it.isLetter() } } /** * Extract ANN ID from a message string. * The ANN ID is expected to be a number enclosed in square brackets (e.g., "[123]"). * @param message The message string to extract the ANN ID from. * @return The extracted ANN ID as an integer, or -1 if not found or invalid. */ fun Get_ANN_ID(message: String) : Int { val matchResult = ann_id_regex.find(message) return matchResult?.groups?.get(1)?.value?.toInt() ?: -1 } /** * Convert an object to a JSON string. * @param data The object to convert. * @return A JSON string representation of the object, or "{}" if conversion fails. */ fun toJsonString(data: Any) : String { return try { objectmapper.writeValueAsString(data) } catch (e: Exception){ "{}" } } /** * Convert a JSON string to an object of the specified class. * @param json The JSON string to convert. * @param clazz The class of the object to convert to. * @return An object of the specified class, or null if conversion fails. */ fun fromJsonString(json: String, clazz: Class) : T? { return try { objectmapper.readValue(json, clazz) } catch (e: Exception){ null } } /** * Convert a JSON string to a JsonNode. * @param data The JSON string to convert. * @return A JsonNode representation of the JSON string, or empty JsonNode if conversion fails. */ fun toJsonNode(data: String) : JsonNode { return try { objectmapper.readTree(data) } catch (e: Exception){ objectmapper.createObjectNode() } } /** * List all audio files (.mp3 and .wav) in the specified directory and its subdirectories. * Only files larger than 1KB are included. * @param p The directory Path to search in * @return A list of absolute paths to the audio files found. */ fun ListAudioFiles(p: Path) : List{ return try{ // find all files that ends with .mp3 or .wav // and find them recursively if (Files.exists(p) && Files.isDirectory(p)){ Files.walk(p) // cari file regular saja .filter { Files.isRegularFile(it)} // size lebih dari 1KB .filter { Files.size(it) > 1024} // extension .mp3 atau .wav .filter { it.name.endsWith(".mp3",true) || it.name.endsWith(".wav",true) } .map { it.toAbsolutePath().toString() } .toList() } else throw Exception() } catch (_ : Exception){ emptyList() } } /** * Converts a size in bytes to a human-readable format. * @param size Size in bytes. * @return A string representing the size in a human-readable format. */ fun SizetoHuman(size: Long): String { return when { size < KB_threshold -> "${size}B" size < MB_threshold -> String.format("%.2f KB", size / KB_threshold) size < GB_threshold -> String.format("%.2f MB", size / MB_threshold) size < TB_threshold -> String.format("%.2f GB", size / GB_threshold) else -> String.format("%.2f TB", size / TB_threshold) } } /** * Get Disk usage using OSHI library. * @param path The path to check disk usage, defaults to the current working directory. * @return A string representing the disk usage of the file system in a human-readable format. */ fun getDiskUsage(path: String = current_directory) : String { return try{ val p = Path.of(path).toFile() if (p.exists() && p.isDirectory){ val total = p.totalSpace val free = p.freeSpace val used = total - free String.format("Total: %s, Used: %s, Free: %s, Usage: %.2f%%", SizetoHuman(total), SizetoHuman(used), SizetoHuman(free), (used.toDouble() / total * 100) ) } else throw Exception() } catch (_ : Exception){ "N/A" } } /** * Get CPU usage using OSHI library. * @param cb A callback function that receives the CPU usage as a string. */ fun getCPUUsage(cb : Consumer){ CoroutineScope(Dispatchers.Default).launch { val prev = processor.systemCpuLoadTicks delay(1000) val current = processor.systemCpuLoadTicks fun delta(t: CentralProcessor.TickType) = current[t.index] - prev[t.index] val idle = delta(CentralProcessor.TickType.IDLE) + delta(CentralProcessor.TickType.IOWAIT) val busy = delta(CentralProcessor.TickType.USER) + delta(CentralProcessor.TickType.SYSTEM) + delta(CentralProcessor.TickType.NICE) + delta(CentralProcessor.TickType.IRQ) + delta(CentralProcessor.TickType.SOFTIRQ)+ delta(CentralProcessor.TickType.STEAL) val total = idle + busy val usage = if (total > 0) { (busy.toDouble() / total) * 100 } else { 0.0 } cb.accept(String.format("%.2f%%", usage)) } } /** * Get RAM usage using OSHI library. * @return A string representing the total, used, and available memory in a human-readable format. */ fun getMemoryUsage() : String{ val totalMemory = memory.total val availableMemory = memory.available val usedMemory = totalMemory - availableMemory return String.format("Total: %s, Used: %s, Available: %s, Usage: %.2f%%", SizetoHuman(totalMemory), SizetoHuman(usedMemory), SizetoHuman(availableMemory) , (usedMemory.toDouble() / totalMemory * 100)) } fun GetNetworkStatus(cb : Consumer>) { val networks: List = si.hardware.networkIFs.toList() networks.forEach { net -> if (net.ifOperStatus==NetworkIF.IfOperStatus.UP){ if (net.iPv4addr.size>0 || net.iPv6addr.size>0){ var ni = NetworkInfoMap[net.name] if (ni == null){ ni = NetworkInformation(net.name, net.displayName, net.macaddr) NetworkInfoMap[net.name] = ni } ni.ipV4addr = net.iPv4addr.toMutableList() ni.ipV6addr = net.iPv6addr.toMutableList() ni.speed = net.speed ni.packetsSent = net.packetsSent ni.packetsRecv = net.packetsRecv if (ni.updateStamp==0L){ ni.bytesSent = net.bytesSent ni.bytesRecv = net.bytesRecv ni.txSpeed = 0 ni.rxSpeed = 0 ni.updateStamp = System.currentTimeMillis() } else { // tx speed = (current bytesSent - previous bytesSent) / (current time - previous time) * 1000 val currentTime = System.currentTimeMillis() ni.txSpeed = ((net.bytesSent - ni.bytesSent) * 1000 / (currentTime - ni.updateStamp)) ni.rxSpeed = ((net.bytesRecv - ni.bytesRecv) * 1000 / (currentTime - ni.updateStamp)) ni.bytesSent = net.bytesSent ni.bytesRecv = net.bytesRecv ni.updateStamp = currentTime } } else if (NetworkInfoMap.contains(net.name)) NetworkInfoMap.remove(net.name) } else if (NetworkInfoMap.contains(net.name)) NetworkInfoMap.remove(net.name) } cb.accept(NetworkInfoMap.values.toList()) } /** * Check if a value is a valid non-blank string. * @param value The value to check. * @return True if the value is a non-blank string, false otherwise. */ fun ValidString(value: Any) : Boolean { return value is String && value.isNotBlank() } /** * Check if all strings in a list are valid non-blank strings. * @param values The list of strings to check. * @return True if all strings in the list are valid non-blank strings, false otherwise. */ fun ValidStrings(values: List) : Boolean{ if (values.isNotEmpty()){ for (v in values){ if (!ValidString(v)){ return false } } return true } return false } /** * Check if a string is a valid file path and the file exists. * @param value The string to check. * @return True if the string is a valid file path, false otherwise. */ fun ValidFile(value : String) : Boolean { if (value.isNotBlank()){ return Files.exists(Path.of(value)) } return false } /** * Check if a string is a valid date in the format "dd/MM/yyyy". * @param value The string to check. * @return True if the string is a valid date, false otherwise. */ fun ValidDate(value: String): Boolean{ return try{ if (ValidString(value)){ dateformat1.parse(value) true } else throw Exception() } catch (_: Exception){ false } } /** * Check if a string is a valid IPv4 address. * @param value The string to check. * @return True if the string is a valid IPv4 address, false otherwise. */ fun ValidIPV4(value: String): Boolean{ return try{ if (ValidString(value)){ val parts = value.split(".") if (parts.size != 4) return false for (part in parts){ val num = part.toInt() if (num !in 0..255) return false } true } else throw Exception() } catch (_: Exception){ false } } /** * Check if a string is a valid date in the format "dd-MM-yyyy". * This format is used for log HTML files. * @param value The string to check. * @return True if the string is a valid date, false otherwise. */ fun ValiDateForLogHtml(value: String): Boolean{ return try{ if (ValidString(value)){ dateformat2.parse(value) true } else throw Exception() } catch (_: Exception){ false } } /** * Check if a string is a valid time in the format "hh:mm:ss". * @param value The string to check. * @return True if the string is a valid time, false otherwise. */ fun ValidTime(value: String): Boolean{ return try{ if (ValidString(value)){ timeformat1.parse(value) true } else throw Exception() } catch (_: Exception){ false } } /** * Check if a string is a valid schedule time in the format "HH:mm". * @param value The string to check. * @return True if the string is a valid schedule time, false otherwise. */ fun ValidScheduleTime(value: String): Boolean{ // format HH:mm try { if (ValidString(value)){ timeformat2.parse(value) return true } } catch (_ : Exception){ } return false } /** * Find a schedule day by its name. * @param value The name of the schedule day to find. * @return The name of the schedule day if found, null otherwise. */ fun FindScheduleDay(value: String) : String? { val sd = ScheduleDay.entries.find { sd -> sd.name == value } return sd?.name } /** * Check if a string is a valid schedule day or a valid date. * A valid schedule day is either one of the ScheduleDay enum names or a date in the format "dd/MM/yyyy". * @param value The string to check. * @return True if the string is a valid schedule day or date, false otherwise. */ fun ValidScheduleDay(value: String) : Boolean { if (ValidString(value)){ // check if value is one of ScheduleDay enum name if (FindScheduleDay(value) != null){ return true } // check if value is in format dd/MM/yyyy return ValidDate(value) } return false } /** * Generate a WAV file name with the current date and time. * The file name format is: [prefix]_ddMMyyyy_HHmmss_[postfix].wav * @param prefix An optional prefix to add before the date and time. * @param postfix An optional postfix to add after the date and time. * @return A string representing the generated WAV file name. */ fun Make_WAV_FileName(prefix: String, postfix: String) : String{ val sb = StringBuilder() if (prefix.isNotEmpty()){sb.append(prefix).append("_")} sb.append(filenameformat.format(LocalDateTime.now())) if (postfix.isNotEmpty()){sb.append("_").append(postfix)} sb.append(".wav") return sb.toString() } /** * Get sensors information using OSHI library. * @return A string representing the CPU temperature, fan speeds, and CPU voltage, or an empty string if not available. */ fun GetSensorsInfo() : String { val cputemp = sensor.cpuTemperature val cpuvolt = sensor.cpuVoltage val fanspeed = sensor.fanSpeeds return if (cpuvolt>0 && cputemp > 0 && fanspeed.isNotEmpty()){ String.format("CPU Temp: %.1f °C\nFan Speeds: %s RPM\nCPU Voltage: %.2f V", sensor.cpuTemperature, sensor.fanSpeeds.joinToString("/"), sensor.cpuVoltage ) } else "" } fun GetUptime() : String { val value = os.systemUptime return if (value>0){ // number of seconds since system boot val hours = value / 3600 val minutes = (value % 3600) / 60 val seconds = value % 60 String.format("%02d:%02d:%02d", hours, minutes, seconds) } else "" } } }