531 lines
21 KiB
Kotlin
531 lines
21 KiB
Kotlin
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<String, NetworkInformation>()
|
|
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<String, Any>())
|
|
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 <T> fromJsonString(json: String, clazz: Class<T>) : 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<String>{
|
|
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<String>){
|
|
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<List<NetworkInformation>>) {
|
|
val networks: List<NetworkIF> = 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<String>) : 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 ""
|
|
}
|
|
}
|
|
|
|
|
|
} |