fuck it
This commit is contained in:
@@ -25,6 +25,9 @@ dependencies {
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0'
|
||||
implementation 'io.github.microutils:kotlin-logging-jvm:3.0.5'
|
||||
implementation "io.ktor:ktor-network:3.4.2"
|
||||
implementation "io.github.oshai:kotlin-logging-jvm:7.0.3"
|
||||
implementation "org.slf4j:slf4j-api:2.0.13"
|
||||
implementation "ch.qos.logback:logback-classic:1.5.6"
|
||||
implementation project(':common')
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import collection.CollectionManager
|
||||
import commands.*
|
||||
import file.FileManager
|
||||
import mu.KotlinLogging
|
||||
import network.NetworkManager
|
||||
import runner.CommandInvoker
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
System.setProperty("slf4j.internal.verbosity", "WARN")
|
||||
|
||||
val filePath = if (args.isNotEmpty()) args[0] else "data.csv"
|
||||
val port = if (args.isNotEmpty() && args.size > 1) args[1].toInt() else 8080
|
||||
|
||||
logger.info{ "[startup] Starting server on port $port with $args args" }
|
||||
|
||||
val fileManager = FileManager(filePath)
|
||||
val manager = CollectionManager()
|
||||
@@ -21,9 +27,9 @@ fun main(args: Array<String>) {
|
||||
registerServerCommands(invoker, manager, fileManager)
|
||||
|
||||
// Сохранение при завершении (Ctrl+C или kill)
|
||||
val server = NetworkManager(port = 8080, invoker = invoker)
|
||||
val server = NetworkManager(port = port, invoker = invoker)
|
||||
Runtime.getRuntime().addShutdownHook(Thread {
|
||||
println("[Server] Сохранение коллекции перед выходом...")
|
||||
logger.info{ "Saving collection before exit..." }
|
||||
fileManager.write(manager.getAll())
|
||||
server.stop()
|
||||
})
|
||||
@@ -31,18 +37,18 @@ fun main(args: Array<String>) {
|
||||
// Консоль на сервере
|
||||
Thread {
|
||||
val scanner = java.util.Scanner(System.`in`)
|
||||
println("[Server] Серверные команды: save, exit")
|
||||
logger.info{ "[Server-console] Server-side commands: save, exit" }
|
||||
while (scanner.hasNextLine()) {
|
||||
when (scanner.nextLine().trim().lowercase()) {
|
||||
"save" -> {
|
||||
fileManager.write(manager.getAll())
|
||||
println("[Server] Коллекция сохранена.")
|
||||
logger.info{ "[Server-console] Collection is saved" }
|
||||
}
|
||||
"exit" -> {
|
||||
println("[Server] Завершение работы...")
|
||||
logger.info{ "[Server-console] Shutting down..." }
|
||||
exitProcess(0)
|
||||
}
|
||||
else -> println("[Server] Доступные команды: save, exit")
|
||||
else -> logger.info{ "[Server-console] Server-side commands: save, exit" }
|
||||
}
|
||||
}
|
||||
}.also { it.isDaemon = true }.start()
|
||||
@@ -56,7 +62,7 @@ fun registerServerCommands(
|
||||
fileManager: FileManager
|
||||
) {
|
||||
listOf(
|
||||
HelpCommand(invoker),
|
||||
// HelpCommand(invoker),
|
||||
InfoCommand(manager),
|
||||
ShowCommand(manager),
|
||||
AddCommand(manager),
|
||||
@@ -67,10 +73,11 @@ fun registerServerCommands(
|
||||
AddIfMaxCommand(manager),
|
||||
AddIfMinCommand(manager),
|
||||
HistoryCommand(invoker),
|
||||
GetCommandsCommand(invoker),
|
||||
SyncCommand(invoker),
|
||||
SumOfMinutesCommand(manager),
|
||||
MinByNameCommand(manager),
|
||||
PrintDescendingMinutesCommand(manager),
|
||||
ExitCommand()
|
||||
).forEach { invoker.register(it) }
|
||||
).forEach { invoker.register(it)}
|
||||
logger.info{ " [startup] Registered commands: ${invoker.getCommands().keys}" }
|
||||
}
|
||||
|
||||
@@ -8,13 +8,14 @@ import java.util.UUID
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
|
||||
class CollectionManager {
|
||||
|
||||
private val collection: TreeSet<HumanBeing> = TreeSet()
|
||||
val initDate: LocalDate = LocalDate.now()
|
||||
|
||||
fun add(humanBeing: HumanBeing): Boolean {
|
||||
logger.info { "[ADD] Создан элемент ${humanBeing.id}" }
|
||||
logger.info { "[ADD] Element created ${humanBeing.id}" }
|
||||
return collection.add(humanBeing)
|
||||
}
|
||||
|
||||
@@ -22,19 +23,19 @@ class CollectionManager {
|
||||
val old = getById(id) ?: return false
|
||||
collection.remove(old)
|
||||
val replaced = updated.copy(id = old.id, creationDate = old.creationDate)
|
||||
logger.info { "[UPDATE] Обновлён элемент ${old.id}" }
|
||||
logger.info { "[UPDATE] Element updated ${old.id}" }
|
||||
return collection.add(replaced)
|
||||
}
|
||||
|
||||
fun removeById(id: UUID): Boolean {
|
||||
val target = getById(id) ?: return false
|
||||
logger.info { "[REMOVE] Удалён элемент ${target.id}" }
|
||||
logger.info { "[REMOVE] Element deleted ${target.id}" }
|
||||
return collection.remove(target)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
collection.clear()
|
||||
logger.info { "[CLEAR] Коллекция очищена" }
|
||||
logger.info { "[CLEAR] Collection cleared" }
|
||||
}
|
||||
|
||||
fun getAll(): TreeSet<HumanBeing> = TreeSet(collection)
|
||||
@@ -78,7 +79,7 @@ class CollectionManager {
|
||||
fun loadFromFile(items: List<HumanBeing>) {
|
||||
collection.clear()
|
||||
items.stream().forEach { collection.add(it) }
|
||||
logger.info { "[LOAD] Загружено ${items.size} элементов" }
|
||||
logger.info { "[LOAD] loaded ${items.size} elements" }
|
||||
}
|
||||
|
||||
fun getInfo(): String =
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
class AddCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class AddCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
override val name = "add"
|
||||
override val description = "добавить новый элемент в коллекцию"
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
override val type = CommandType.MODEL
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
humanBeing ?: return "[Ошибка] Объект HumanBeing не передан."
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
||||
return if (manager.add(humanBeing))
|
||||
"Элемент добавлен: ${humanBeing.name} [${humanBeing.id}]"
|
||||
Response(true, "Элемент добавлен: ${humanBeing.name} [${humanBeing.id}]")
|
||||
else
|
||||
"Элемент уже существует в коллекции."
|
||||
Response(false, "Элемент уже существует в коллекции.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,22 @@ package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
class AddIfMaxCommand(private val manager: CollectionManager) : Command {
|
||||
override val name = "add_if_max"
|
||||
override val description = "добавить элемент, если он больше максимального"
|
||||
override val type = CommandType.MODEL
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
humanBeing ?: return "[Ошибка] Объект HumanBeing не передан."
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
||||
val max = manager.getMax()
|
||||
return if (max == null || humanBeing > max) {
|
||||
manager.add(humanBeing)
|
||||
"Элемент добавлен (превышает максимум)."
|
||||
Response(true, "Элемент добавлен (превышает максимум).")
|
||||
} else {
|
||||
"Элемент не добавлен: не превышает текущий максимум (${max.name})."
|
||||
Response(false, "Элемент не добавлен: не превышает текущий максимум (${max.name}).")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,25 @@ package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import models.HumanBeing
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Contextual
|
||||
import network.Response
|
||||
|
||||
class AddIfMinCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class AddIfMinCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
override val name = "add_if_min"
|
||||
override val description = "добавить элемент, если он меньше минимального"
|
||||
override val type = CommandType.MODEL
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
humanBeing ?: return "[Ошибка] Объект HumanBeing не передан."
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
||||
val min = manager.getMin()
|
||||
return if (min == null || humanBeing < min) {
|
||||
manager.add(humanBeing)
|
||||
"Элемент добавлен (меньше минимума)."
|
||||
Response(true, "Элемент добавлен (меньше минимума).")
|
||||
} else {
|
||||
"Элемент не добавлен: не меньше текущего минимума (${min.name})."
|
||||
Response(false, "Элемент не добавлен: не меньше текущего минимума (${min.name}).")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
class ClearCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class ClearCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
override val name = "clear"
|
||||
override val description = "очистить коллекцию"
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
override val type = CommandType.SIMPLE
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
manager.clear()
|
||||
return "Коллекция очищена."
|
||||
return Response(true, "Коллекция очищена.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
/**
|
||||
* Базовый интерфейс команды.
|
||||
@@ -11,11 +12,13 @@ import models.HumanBeing
|
||||
interface Command {
|
||||
val name: String
|
||||
val description: String
|
||||
val type : CommandType
|
||||
val args : List<CommandArgType>
|
||||
|
||||
/**
|
||||
* @param args строковые аргументы (UUID и т.п.)
|
||||
* @param humanBeing объект из запроса — только для add/update/add_if_max/add_if_min
|
||||
* @return строка-результат для клиента
|
||||
*/
|
||||
fun execute(args: List<String>, humanBeing: HumanBeing? = null): String
|
||||
fun execute(args: List<String>, humanBeing: HumanBeing? = null): Response
|
||||
}
|
||||
|
||||
@@ -3,11 +3,16 @@ package commands
|
||||
/**
|
||||
* Завершает программу без сохранения.
|
||||
*/
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
@Serializable
|
||||
class ExitCommand() : Command {
|
||||
override val name = "exit"
|
||||
override val description = "завершить программу"
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String = ""
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response = Response(true, "")
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package commands
|
||||
|
||||
import models.HumanBeing
|
||||
import runner.CommandInvoker
|
||||
|
||||
class GetCommandsCommand (
|
||||
private val invoker: CommandInvoker
|
||||
): Command {
|
||||
override val name: String = "GetCommands"
|
||||
override val description: String = "Получает все доступные с сервера команды"
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
return invoker.getCommands().toString()
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package commands
|
||||
|
||||
import runner.CommandInvoker
|
||||
import models.HumanBeing
|
||||
|
||||
class HelpCommand(private val invoker: CommandInvoker) : Command {
|
||||
override val name = "help"
|
||||
override val description = "вывести справку по доступным командам"
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String =
|
||||
invoker.getCommands().values
|
||||
.sortedBy { it.name }
|
||||
.joinToString("\n") { " ${it.name.padEnd(40)} — ${it.description}" }
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package commands
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import runner.CommandInvoker
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
class HistoryCommand(private val invoker: CommandInvoker) : Command {
|
||||
@Serializable
|
||||
class HistoryCommand(@Contextual private val invoker: CommandInvoker) : Command {
|
||||
override val name = "history"
|
||||
override val description = "вывести последние 12 команд"
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
val history = invoker.getHistory()
|
||||
if (history.isEmpty()) return "История пуста."
|
||||
return history.mapIndexed { i, cmd -> " ${i + 1}. $cmd" }.joinToString("\n")
|
||||
if (history.isEmpty()) return Response(true, "История пуста.")
|
||||
return Response(true, history.mapIndexed { i, cmd -> " ${i + 1}. $cmd" }.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
class InfoCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class InfoCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
override val name = "info"
|
||||
override val description = "вывести информацию о коллекции"
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String =
|
||||
manager.getInfo()
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response =
|
||||
Response(true, manager.getInfo())
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
/**
|
||||
* Выводит любой элемент с минимальным значением поля [HumanBeing.name].
|
||||
*/
|
||||
class MinByNameCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class MinByNameCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
override val name = "min_by_name"
|
||||
override val description = "вывести элемент с минимальным именем"
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String =
|
||||
manager.minByName()?.toString() ?: "Коллекция пуста."
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response =
|
||||
Response(true, manager.minByName()?.toString() ?: "Коллекция пуста.")
|
||||
}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
/**
|
||||
* Выводит значения поля [models.HumanBeing.minutesOfWaiting] в порядке убывания.
|
||||
*/
|
||||
class PrintDescendingMinutesCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class PrintDescendingMinutesCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
override val name = "print_field_descending_minutes_of_waiting"
|
||||
override val description = "вывести minutesOfWaiting в порядке убывания"
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
val values = manager.getMinutesOfWaitingDescending()
|
||||
return if (values.isEmpty()) "Коллекция пуста."
|
||||
else values.joinToString("\n")
|
||||
return if (values.isEmpty()) Response(true, "Коллекция пуста.")
|
||||
else Response(true, values.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
class RemoveByIdCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class RemoveByIdCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
override val name = "remove_by_id"
|
||||
override val description = "удалить элемент по id: remove_by_id <uuid>"
|
||||
override val type = CommandType.MODEL
|
||||
override val args = listOf(CommandArgType.STRING)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
if (args.isEmpty()) return "[Ошибка] Укажите id. Пример: remove_by_id <uuid>"
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
if (args.isEmpty()) return Response(false, "[Ошибка] Укажите id. Пример: remove_by_id <uuid>")
|
||||
|
||||
val id = try {
|
||||
UUID.fromString(args[0])
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return "[Ошибка] Некорректный UUID: '${args[0]}'"
|
||||
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
|
||||
}
|
||||
|
||||
return if (manager.removeById(id)) "Элемент удалён."
|
||||
else "[Ошибка] Элемент с id '$id' не найден."
|
||||
return if (manager.removeById(id)) Response(true, "Элемент удалён.")
|
||||
else Response(false, "[Ошибка] Элемент с id '$id' не найден.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,23 @@ package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import file.FileManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
@Serializable
|
||||
class SaveCommand(
|
||||
private val manager: CollectionManager,
|
||||
private val fileManager: FileManager
|
||||
@Contextual private val manager: CollectionManager,
|
||||
@Contextual private val fileManager: FileManager
|
||||
) : Command {
|
||||
override val name = "save"
|
||||
override val description = "[SERVER] сохранить коллекцию в файл"
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
return if (fileManager.write(manager.getAll())) "Коллекция сохранена."
|
||||
else "[Ошибка] Не удалось сохранить коллекцию."
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
return if (fileManager.write(manager.getAll())) Response(true, "Коллекция сохранена.")
|
||||
else Response(false, "[Ошибка] Не удалось сохранить коллекцию.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
class ShowCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class ShowCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
override val name = "show"
|
||||
override val description = "вывести все элементы коллекции"
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
if (manager.isEmpty()) return "Коллекция пуста."
|
||||
return manager.getAll().joinToString("\n") { it.toString() }
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
if (manager.isEmpty()) return Response(true, "Коллекция пуста.")
|
||||
return Response(true, manager.getAll().joinToString("\n") { it.toString() })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
/**
|
||||
* Выводит сумму значений поля [models.HumanBeing.minutesOfWaiting].
|
||||
*/
|
||||
class SumOfMinutesCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class SumOfMinutesCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
override val name = "sum_of_minutes_of_waiting"
|
||||
override val description = "вывести сумму minutesOfWaiting всех элементов"
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String =
|
||||
"Сумма minutesOfWaiting: ${manager.sumOfMinutesOfWaiting()}"
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response =
|
||||
Response(true, "Сумма minutesOfWaiting: ${manager.sumOfMinutesOfWaiting()}")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package commands
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.CommandInfo
|
||||
import network.Response
|
||||
import runner.CommandInvoker
|
||||
|
||||
@Serializable
|
||||
class SyncCommand(
|
||||
@Contextual private val invoker: CommandInvoker
|
||||
) : Command {
|
||||
override val name: String = "sync"
|
||||
override val description: String = "Получает все доступные с сервера команды"
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
|
||||
val commands = mutableMapOf<String, Command>()
|
||||
|
||||
for (command in invoker.getCommands().values) {
|
||||
if (command.name == "sync" || command.name == "exit") {
|
||||
continue
|
||||
}
|
||||
commands[command.name] = command
|
||||
}
|
||||
|
||||
val commandInfos = commands.values.map { command ->
|
||||
CommandInfo(
|
||||
name = command.name,
|
||||
description = command.description,
|
||||
type = command.type,
|
||||
args = command.args
|
||||
)
|
||||
}
|
||||
|
||||
return Response(
|
||||
success = true,
|
||||
message = "хуй",
|
||||
commands = commandInfos
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,30 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
import java.util.UUID
|
||||
|
||||
class UpdateCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class UpdateCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
override val name = "update"
|
||||
override val description = "обновить элемент по id: update <uuid>"
|
||||
override val type = CommandType.MODEL
|
||||
override val args = listOf(CommandArgType.STRING)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
if (args.isEmpty()) return "[Ошибка] Укажите id. Пример: update <uuid>"
|
||||
humanBeing ?: return "[Ошибка] Объект HumanBeing не передан."
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
if (args.isEmpty()) return Response(false, "[Ошибка] Укажите id. Пример: update <uuid>")
|
||||
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
||||
|
||||
val id = try {
|
||||
UUID.fromString(args[0])
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return "[Ошибка] Некорректный UUID: '${args[0]}'"
|
||||
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
|
||||
}
|
||||
|
||||
return if (manager.update(id, humanBeing)) "Элемент обновлён."
|
||||
else "[Ошибка] Элемент с id '$id' не найден."
|
||||
return if (manager.update(id, humanBeing)) Response(true, "Элемент обновлён.")
|
||||
else Response(false, "[Ошибка] Элемент с id '$id' не найден.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import models.Car
|
||||
import models.Coordinates
|
||||
import models.HumanBeing
|
||||
import models.WeaponType
|
||||
import mu.KotlinLogging
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.OutputStreamWriter
|
||||
@@ -12,6 +13,8 @@ import java.time.format.DateTimeParseException
|
||||
import java.util.Scanner
|
||||
import java.util.UUID
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
/**
|
||||
* Менеджер файлового хранилища коллекции.
|
||||
*
|
||||
@@ -45,12 +48,12 @@ class FileManager(private val filePath: String) {
|
||||
val file = File(filePath)
|
||||
|
||||
if (!file.exists()) {
|
||||
System.err.println("Файл '$filePath' не найден. Коллекция будет пустой.")
|
||||
logger.warn{"There's no '$filePath'. Collection will be empty."}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if (!file.canRead()) {
|
||||
System.err.println("Нет прав на чтение файла '$filePath'. Коллекция будет пустой.")
|
||||
logger.warn{"Permission denied: '$filePath'. Collection will be empty."}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@@ -63,20 +66,23 @@ class FileManager(private val filePath: String) {
|
||||
val line = scanner.nextLine().trim()
|
||||
lineNumber++
|
||||
|
||||
// Пропускаем заголовок и пустые строки
|
||||
// skip header and empty lines
|
||||
if (lineNumber == 1 && line.startsWith("id")) continue
|
||||
if (line.isBlank()) continue
|
||||
|
||||
parseLine(line, lineNumber)
|
||||
?.let { result.add(it) }
|
||||
?: System.err.println("Строка $lineNumber пропущена: некорректные данные → '$line'")
|
||||
val parsedLine = parseLine(line, lineNumber)
|
||||
if (parsedLine != null) {
|
||||
result.add(parsedLine)
|
||||
} else {
|
||||
logger.warn { "Line $lineNumber of $filePath is skipped: invalid data → '$line'" }
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
System.err.println("Файл не найден во время чтения: ${e.message}")
|
||||
logger.warn(e) {"File not found: ${e.message}"}
|
||||
}
|
||||
|
||||
println("Загружено элементов: ${result.size} из файла '$filePath'")
|
||||
logger.info {"${result.size} elements loaded from'$filePath'"}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -91,7 +97,7 @@ class FileManager(private val filePath: String) {
|
||||
return try {
|
||||
val parts = line.split(DELIMITER, limit = 12)
|
||||
if (parts.size < 12) {
|
||||
System.err.println("Строка $lineNumber: ожидалось 12 полей, найдено ${parts.size}")
|
||||
logger.warn {"Line $lineNumber: expected 12 fields, found ${parts.size}"}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -111,24 +117,16 @@ class FileManager(private val filePath: String) {
|
||||
}
|
||||
val carCool = parts[11].trim().toBoolean()
|
||||
|
||||
// самая лучшая валидация (100%)
|
||||
require(name.isNotBlank()) { "name не может быть пустым" }
|
||||
|
||||
// от такого может быть диабет
|
||||
HumanBeing(
|
||||
id = id, name = name, coordinates = Coordinates(coordX, coordY),
|
||||
creationDate = creationDate, realHero = realHero, hasToothpick = hasToothpick,
|
||||
impactSpeed = impactSpeed, soundtrackName = soundtrackName,
|
||||
minutesOfWaiting = minutesOfWaiting, weaponType = weaponType, car = Car(carCool)
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
System.err.println("Строка $lineNumber — ошибка данных: ${e.message}")
|
||||
null
|
||||
} catch (e: DateTimeParseException) {
|
||||
System.err.println("Строка $lineNumber — неверный формат даты: ${e.message}")
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
System.err.println("Строка $lineNumber — неожиданная ошибка: ${e.message}")
|
||||
logger.warn(e) {"Line $lineNumber: ${e.message}"}
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -143,7 +141,7 @@ class FileManager(private val filePath: String) {
|
||||
val file = File(filePath)
|
||||
|
||||
if (file.exists() && !file.canWrite()) {
|
||||
System.err.println("Нет прав на запись в файл '$filePath'.")
|
||||
logger.warn{"Permission denied: '$filePath'."}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -156,13 +154,13 @@ class FileManager(private val filePath: String) {
|
||||
writer.write("\n")
|
||||
}
|
||||
}
|
||||
println("Коллекция сохранена в '$filePath' (${items.size} элементов).")
|
||||
logger.info {"Collection saved to'$filePath' (${items.size} elements)."}
|
||||
true
|
||||
} catch (e: SecurityException) {
|
||||
println("Отказано в доступе к файлу '$filePath': ${e.message}")
|
||||
logger.warn(e) {"Permission denied: '$filePath'."}
|
||||
false
|
||||
} catch (e: Exception) {
|
||||
println("Ошибка записи в файл: ${e.message}")
|
||||
logger.warn(e) {"An error occurred: '$filePath'."}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package network
|
||||
|
||||
import kotlinx.serialization.SerializationException
|
||||
import lab6.prog.network.AppJson
|
||||
import mu.KotlinLogging
|
||||
import runner.CommandInvoker
|
||||
import java.net.DatagramPacket
|
||||
import java.net.DatagramSocket
|
||||
import java.net.InetAddress
|
||||
|
||||
private const val BUFFER_SIZE = 65507
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
/**
|
||||
* UDP-сервер на основе DatagramSocket (датаграммы).
|
||||
@@ -24,37 +24,30 @@ class NetworkManager(
|
||||
private val socket = DatagramSocket(port)
|
||||
|
||||
fun start() {
|
||||
println("[Server] Запущен на порту $port. Ожидание клиентов...")
|
||||
val buf = ByteArray(BUFFER_SIZE)
|
||||
logger.info{"Server started on port $port. Waiting for connection..."}
|
||||
val buf = ByteArray(65507)
|
||||
|
||||
while (!socket.isClosed) {
|
||||
try {
|
||||
// 1. Принять датаграмму
|
||||
val packet = DatagramPacket(buf, buf.size)
|
||||
socket.receive(packet)
|
||||
|
||||
val json = String(packet.data, 0, packet.length, Charsets.UTF_8)
|
||||
|
||||
// 2. Десериализовать запрос
|
||||
val request = try {
|
||||
AppJson.decodeFromString<Request>(json)
|
||||
} catch (e: SerializationException) {
|
||||
System.err.println("[Server] Некорректный JSON: ${e.message}")
|
||||
logger.warn(e) { "Invalid request: ${e.message}" }
|
||||
sendResponse(Response(false, "Некорректный формат запроса"), packet.address, packet.port)
|
||||
continue
|
||||
}
|
||||
logger.info{"'${request.commandName}' from ${packet.address}:${packet.port}."}
|
||||
|
||||
println("[Server] '${request.commandName}' от ${packet.address}:${packet.port}")
|
||||
|
||||
// 3. Выполнить и получить ответ
|
||||
val response = invoker.execute(request)
|
||||
|
||||
// 4. Отправить ответ
|
||||
sendResponse(response, packet.address, packet.port)
|
||||
|
||||
} catch (e: Exception) {
|
||||
if (!socket.isClosed)
|
||||
System.err.println("[Server] Ошибка: ${e.message}")
|
||||
logger.warn(e) { "Error: ${e.message}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +58,7 @@ class NetworkManager(
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
println("[Server] Остановка...")
|
||||
logger.info{ "Shutting down..." }
|
||||
socket.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ import network.Response
|
||||
*/
|
||||
class CommandInvoker {
|
||||
|
||||
private val serverOnlyCommands = setOf("save")
|
||||
private val serverOnlyCommands = setOf("save", "sync-server")
|
||||
private val commands = mutableMapOf<String, Command>()
|
||||
private val history = ArrayDeque<String>()
|
||||
private val history = ArrayDeque<String>()
|
||||
|
||||
fun register(command: Command) {
|
||||
commands[command.name] = command
|
||||
@@ -31,8 +31,7 @@ class CommandInvoker {
|
||||
addToHistory(request.commandName)
|
||||
|
||||
return try {
|
||||
val result = command.execute(request.args, request.humanBeing)
|
||||
Response(true, result)
|
||||
command.execute(request.args, request.humanBeing)
|
||||
} catch (e: Exception) {
|
||||
Response(false, "[Ошибка выполнения] ${e.message}")
|
||||
}
|
||||
|
||||
@@ -1,44 +1,20 @@
|
||||
<configuration debug="false" statusListenerClass="ch.qos.logback.core.status.NopStatusListener">
|
||||
|
||||
<appender name="ALL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>logs/all.log</file>
|
||||
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>logs/archived/all-%d{yyyy-MM-dd_HH-mm}.%i.log</fileNamePattern>
|
||||
|
||||
<maxFileSize>5MB</maxFileSize>
|
||||
|
||||
<maxHistory>30</maxHistory>
|
||||
|
||||
<totalSizeCap>100MB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<configuration>
|
||||
<appender name="server_log" class="ch.qos.logback.core.FileAppender">
|
||||
<file>logs/server.log</file>
|
||||
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>logs/errors.log</file>
|
||||
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>WARN</level>
|
||||
</filter>
|
||||
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>logs/archived/errors-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<maxFileSize>2MB</maxFileSize>
|
||||
<maxHistory>10</maxHistory>
|
||||
</rollingPolicy>
|
||||
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="ALL_FILE" />
|
||||
<appender-ref ref="ERROR_FILE" />
|
||||
<root level="TRACE">
|
||||
<appender-ref ref="server_log" />
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user