Database works

This commit is contained in:
2026-05-15 18:44:52 +03:00
parent 547a77d52a
commit 92d48211a5
17 changed files with 221 additions and 290 deletions
+23
View File
@@ -15,3 +15,26 @@
END $$;
2026-05-13 20:53:18.805 [main] DEBUG Exposed - SELECT humanbeing.id, humanbeing."name", humanbeing.coordinates, humanbeing.creation_date, humanbeing.real_hero, humanbeing.has_toothpick, humanbeing.impact_speed, humanbeing.soundtrack_name, humanbeing.minutes_of_waiting, humanbeing.car, humanbeing.weapon_type FROM humanbeing
2026-05-14 12:02:58.675 [main] DEBUG Exposed -
DO $$ BEGIN
CREATE TYPE weapontype AS ENUM ('hammer', 'shotgun', 'bat');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
2026-05-14 12:02:59.051 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS coordinates (id uuid PRIMARY KEY, x BIGINT NULL, y INT NULL)
2026-05-14 12:02:59.301 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS car (id uuid PRIMARY KEY, cool BOOLEAN NULL)
2026-05-14 12:02:59.750 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS "user" (id uuid PRIMARY KEY, username TEXT NOT NULL, "password" TEXT NOT NULL, email TEXT NOT NULL)
2026-05-14 12:02:59.946 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS humanbeing (id uuid PRIMARY KEY, owner_id uuid NOT NULL, "name" TEXT NOT NULL, coordinates uuid NOT NULL, creation_date DATE NOT NULL, real_hero BOOLEAN NOT NULL, has_toothpick BOOLEAN NOT NULL, impact_speed DOUBLE PRECISION NOT NULL, soundtrack_name TEXT NOT NULL, minutes_of_waiting REAL NOT NULL, car uuid NOT NULL, weapon_type weapontype DEFAULT NULL NULL, CONSTRAINT fk_humanbeing_owner_id__id FOREIGN KEY (owner_id) REFERENCES "user"(id) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT fk_humanbeing_coordinates__id FOREIGN KEY (coordinates) REFERENCES coordinates(id) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT fk_humanbeing_car__id FOREIGN KEY (car) REFERENCES car(id) ON DELETE RESTRICT ON UPDATE RESTRICT)
2026-05-14 12:03:00.829 [main] DEBUG Exposed - SELECT humanbeing.id, humanbeing.owner_id, humanbeing."name", humanbeing.coordinates, humanbeing.creation_date, humanbeing.real_hero, humanbeing.has_toothpick, humanbeing.impact_speed, humanbeing.soundtrack_name, humanbeing.minutes_of_waiting, humanbeing.car, humanbeing.weapon_type FROM humanbeing
2026-05-15 13:21:48.675 [main] DEBUG Exposed -
DO $$ BEGIN
CREATE TYPE weapontype AS ENUM ('HAMMER', 'SHOTGUN', 'BAT');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
2026-05-15 13:21:49.467 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS coordinates (id uuid PRIMARY KEY, x BIGINT NULL, y INT NULL)
2026-05-15 13:21:49.922 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS car (id uuid PRIMARY KEY, cool BOOLEAN NULL)
2026-05-15 13:21:50.539 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS humanbeing (id uuid PRIMARY KEY, owner_id uuid NOT NULL, "name" TEXT NOT NULL, coordinates uuid NOT NULL, creation_date DATE NOT NULL, real_hero BOOLEAN NOT NULL, has_toothpick BOOLEAN NOT NULL, impact_speed DOUBLE PRECISION NOT NULL, soundtrack_name TEXT NOT NULL, minutes_of_waiting REAL NOT NULL, car uuid NOT NULL, weapon_type weapontype DEFAULT NULL NULL, CONSTRAINT fk_humanbeing_owner_id__id FOREIGN KEY (owner_id) REFERENCES "user"(id) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT fk_humanbeing_coordinates__id FOREIGN KEY (coordinates) REFERENCES coordinates(id) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT fk_humanbeing_car__id FOREIGN KEY (car) REFERENCES car(id) ON DELETE RESTRICT ON UPDATE RESTRICT)
2026-05-15 13:21:51.146 [main] DEBUG Exposed - SELECT humanbeing.id, humanbeing.owner_id, humanbeing."name", humanbeing.coordinates, humanbeing.creation_date, humanbeing.real_hero, humanbeing.has_toothpick, humanbeing.impact_speed, humanbeing.soundtrack_name, humanbeing.minutes_of_waiting, humanbeing.car, humanbeing.weapon_type FROM humanbeing
+17 -16
View File
@@ -1,6 +1,7 @@
import collection.CollectionManager
import commands.*
import file.FileManager
import database.DatabaseManager
import database.Repository
import mu.KotlinLogging
import network.NetworkManager
import runner.CommandInvoker
@@ -10,21 +11,23 @@ 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 database = DatabaseManager()
database.start()
val repository = Repository()
val manager = CollectionManager()
val invoker = CommandInvoker()
// Загружаем коллекцию из файла
manager.loadFromFile(fileManager.read())
// let's pretend that i'm in
manager.loadFromFile(repository.read())
// Регистрируем команды
registerServerCommands(invoker, manager, fileManager)
registerServerCommands(invoker, manager, repository)
// Сохранение при завершении (Ctrl+C или kill)
val server = NetworkManager(port = port, invoker = invoker)
@@ -36,14 +39,14 @@ fun main(args: Array<String>) {
// Консоль на сервере
Thread {
val scanner = java.util.Scanner(System.`in`)
logger.info{ "[Server-console] Server-side commands: save, exit" }
logger.info{ "[Server-console] Server-side commands: exit" }
while (scanner.hasNextLine()) {
when (scanner.nextLine().trim().lowercase()) {
"exit" -> {
logger.info{ "[Server-console] Shutting down..." }
exitProcess(0)
}
else -> logger.info{ "[Server-console] Server-side commands: save, exit" }
else -> logger.info{ "[Server-console] Server-side commands: exit" }
}
}
}.also { it.isDaemon = true }.start()
@@ -54,19 +57,17 @@ fun main(args: Array<String>) {
fun registerServerCommands(
invoker: CommandInvoker,
manager: CollectionManager,
fileManager: FileManager
repository: Repository
) {
listOf(
// HelpCommand(invoker),
InfoCommand(manager),
ShowCommand(manager),
AddCommand(manager),
UpdateCommand(manager),
RemoveByIdCommand(manager),
AddCommand(manager, repository),
UpdateCommand(manager, repository),
RemoveByIdCommand(manager, repository),
ClearCommand(manager),
SaveCommand(manager, fileManager), // только на сервере
AddIfMaxCommand(manager),
AddIfMinCommand(manager),
AddIfMaxCommand(manager, repository),
AddIfMinCommand(manager, repository),
HistoryCommand(invoker),
SyncCommand(invoker),
SumOfMinutesCommand(manager),
@@ -1,13 +1,17 @@
package commands
import collection.CollectionManager
import database.Repository
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing
import network.Response
@Serializable
class AddCommand(@Contextual private val manager: CollectionManager) : Command {
class AddCommand(
@Contextual private val manager: CollectionManager,
@Contextual private val repository: Repository
) : Command {
override val name = "add"
override val description = "добавить новый элемент в коллекцию"
override val args = listOf(CommandArgType.NONE)
@@ -15,8 +19,12 @@ class AddCommand(@Contextual private val manager: CollectionManager) : Command {
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
humanBeing.id = repository.write(humanBeing)
humanBeing.id ?: return Response(false, "Не удалось добавить элемент")
return if (manager.add(humanBeing))
Response(true, "Элемент добавлен: ${humanBeing.name} [${humanBeing.id}]")
else
Response(false, "Элемент уже существует в коллекции.")
}
@@ -1,10 +1,17 @@
package commands
import collection.CollectionManager
import database.Repository
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing
import network.Response
class AddIfMaxCommand(private val manager: CollectionManager) : Command {
@Serializable
class AddIfMaxCommand(
@Contextual val manager: CollectionManager,
@Contextual private val repository: Repository
) : Command {
override val name = "add_if_max"
override val description = "добавить элемент, если он больше максимального"
override val type = CommandType.MODEL
@@ -14,6 +21,8 @@ class AddIfMaxCommand(private val manager: CollectionManager) : Command {
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
val max = manager.getMax()
return if (max == null || humanBeing > max) {
humanBeing.id = repository.write(humanBeing)
humanBeing.id ?: return Response(false, "Не удалось добавить элемент")
manager.add(humanBeing)
Response(true, "Элемент добавлен (превышает максимум).")
} else {
@@ -1,13 +1,17 @@
package commands
import collection.CollectionManager
import database.Repository
import models.HumanBeing
import kotlinx.serialization.Serializable
import kotlinx.serialization.Contextual
import network.Response
@Serializable
class AddIfMinCommand(@Contextual private val manager: CollectionManager) : Command {
class AddIfMinCommand(
@Contextual private val manager: CollectionManager,
@Contextual private val repository: Repository
) : Command {
override val name = "add_if_min"
override val description = "добавить элемент, если он меньше минимального"
override val type = CommandType.MODEL
@@ -17,6 +21,8 @@ class AddIfMinCommand(@Contextual private val manager: CollectionManager) : Comm
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
val min = manager.getMin()
return if (min == null || humanBeing < min) {
humanBeing.id = repository.write(humanBeing)
humanBeing.id ?: return Response(false, "Не удалось добавить элемент")
manager.add(humanBeing)
Response(true, "Элемент добавлен (меньше минимума).")
} else {
@@ -1,6 +1,7 @@
package commands
import collection.CollectionManager
import database.Repository
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import java.util.UUID
@@ -8,10 +9,13 @@ import models.HumanBeing
import network.Response
@Serializable
class RemoveByIdCommand(@Contextual private val manager: CollectionManager) : Command {
class RemoveByIdCommand(
@Contextual private val manager: CollectionManager,
@Contextual private val repository: Repository
) : Command {
override val name = "remove_by_id"
override val description = "удалить элемент по id: remove_by_id <uuid>"
override val type = CommandType.MODEL
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.STRING)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
@@ -23,7 +27,11 @@ class RemoveByIdCommand(@Contextual private val manager: CollectionManager) : Co
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
}
return if (manager.removeById(id)) Response(true, "Элемент удалён.")
else Response(false, "[Ошибка] Элемент с id '$id' не найден.")
if (repository.delete(id)) {
manager.removeById(id)
return Response(true, "Элемент удалён.")
} else {
return Response(false, "[Ошибка] Элемент с id '$id' не найден и не был удалён.")
}
}
}
@@ -1,26 +0,0 @@
package commands
import collection.CollectionManager
import file.FileManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing
import network.Response
import java.util.UUID
@Serializable
class SaveCommand(
@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?): Response {
val dummyUser: UUID = UUID.fromString("68db6bf5-65ce-46c7-881d-c85b23254f92")
return if (fileManager.write(manager.getAll(), dummyUser)) Response(true, "Коллекция сохранена.")
else Response(false, "[Ошибка] Не удалось сохранить коллекцию.")
}
}
@@ -1,6 +1,7 @@
package commands
import collection.CollectionManager
import database.Repository
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing
@@ -8,14 +9,17 @@ import network.Response
import java.util.UUID
@Serializable
class UpdateCommand(@Contextual private val manager: CollectionManager) : Command {
class UpdateCommand(
@Contextual private val manager: CollectionManager,
@Contextual private val repository: Repository
) : 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?): Response {
if (args.isEmpty()) return Response(false, "[Ошибка] Укажите id. Пример: update <uuid>")
if (args.isEmpty()) return Response(false, "[Ошибка] Укажите id. Пример: update <UUID>")
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
val id = try {
@@ -24,7 +28,11 @@ class UpdateCommand(@Contextual private val manager: CollectionManager) : Comman
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
}
return if (manager.update(id, humanBeing)) Response(true, "Элемент обновлён.")
else Response(false, "[Ошибка] Элемент с id '$id' не найден.")
if (repository.update(id, humanBeing)) {
manager.update(id, humanBeing)
return Response(true, "Элемент обновлён.")
} else {
return Response(false, "[Ошибка] Элемент не найден и не был обновлён.")
}
}
}
@@ -28,7 +28,7 @@ class DatabaseManager {
exec(
"""
DO ${'$'}${'$'} BEGIN
CREATE TYPE weapontype AS ENUM ('hammer', 'shotgun', 'bat');
CREATE TYPE weapontype AS ENUM ('HAMMER', 'SHOTGUN', 'BAT');
EXCEPTION
WHEN duplicate_object THEN null;
END ${'$'}${'$'};
@@ -52,12 +52,4 @@ class DatabaseManager {
}
return smth
}
}
fun main(args: Array<String>) {
val manager = DatabaseManager()
println("here we go!")
manager.start()
println(manager.test())
println("here we don't go!")
}
}
+110 -16
View File
@@ -5,25 +5,63 @@ import database.tables.CoordinatesTable
import database.tables.HumanBeingTable
import database.tables.UserTable
import io.github.oshai.kotlinlogging.KotlinLogging
import models.Car
import models.Coordinates
import models.HumanBeing
import org.jetbrains.exposed.v1.core.*
import org.jetbrains.exposed.v1.jdbc.*
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import java.util.UUID
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
private val logger = KotlinLogging.logger {}
class Repository(private val filePath: String) {
class Repository() {
//fun read(): List<HumanBeing> {}
@OptIn(ExperimentalUuidApi::class)
fun write(items: Collection<HumanBeing>, userId: UUID): Boolean {
fun read(): List<HumanBeing> {
val beings = mutableListOf<HumanBeing>()
transaction {
val humans = HumanBeingTable
.selectAll()
for (item in items) {
for (human in humans) {
logger.debug { "read. $human" }
val cord = CoordinatesTable
.select(CoordinatesTable.x, CoordinatesTable.y)
.where(CoordinatesTable.id eq human[HumanBeingTable.coordinates])
.single()
val car = CarTable
.select(CarTable.cool)
.where(CarTable.id eq human[HumanBeingTable.car])
.single()
val newCords = Coordinates(cord[CoordinatesTable.x]!!, cord[CoordinatesTable.y]!!)
val newCar = Car(car[CarTable.cool]!!)
val newBeing = HumanBeing(
name = human[HumanBeingTable.name],
ownerId = human[HumanBeingTable.owner_id].value,
coordinates = newCords,
car = newCar,
id = human[HumanBeingTable.id].value,
creationDate = human[HumanBeingTable.creationDate],
realHero = human[HumanBeingTable.realHero],
hasToothpick = human[HumanBeingTable.hasToothpick],
impactSpeed = human[HumanBeingTable.impactSpeed],
soundtrackName = human[HumanBeingTable.soundtrackName],
minutesOfWaiting = human[HumanBeingTable.minutesOfWaiting],
weaponType = human[HumanBeingTable.weaponType]
)
beings.add(newBeing)
}
}
return beings
}
fun write(item: HumanBeing): UUID? {
var humanId: UUID? = null
try {
transaction {
val cords = CoordinatesTable.insertAndGetId {
it[x] = item.coordinates.x
it[y] = item.coordinates.y
@@ -33,15 +71,9 @@ class Repository(private val filePath: String) {
it[cool] = item.car.cool
}
val user = UserTable
.select(UserTable.id)
.where { UserTable.id eq userId }
.first()
HumanBeingTable.insert {
it[owner_id] = userId
humanId = HumanBeingTable.insertAndGetId {
it[owner_id] = item.ownerId
it[name] = item.name
it[id] = user[UserTable.id] // it fucking autoincrements (check perplexity)
it[coordinates] = cords
it[creationDate] = item.creationDate
it[realHero] = item.realHero
@@ -51,9 +83,71 @@ class Repository(private val filePath: String) {
it[minutesOfWaiting] = item.minutesOfWaiting
it[car] = cars
it[weaponType] = item.weaponType
}.value
}
} catch (e: Exception) {
logger.error(e) { "write failed. Here what's going on: \n $e \n " }
return humanId
}
return humanId
}
fun update(id: UUID, item: HumanBeing): Boolean {
var allGood = false
transaction {
val check = HumanBeingTable
.select(HumanBeingTable.id)
.where(HumanBeingTable.id eq id)
.singleOrNull()
if (check != null) {
val cordsInBeing = HumanBeingTable
.select(HumanBeingTable.coordinates)
.where(HumanBeingTable.id eq id)
.single()
CoordinatesTable.update(
{ CoordinatesTable.id eq cordsInBeing[HumanBeingTable.coordinates] }
) {
it[x] = item.coordinates.x
it[y] = item.coordinates.y
}
CarTable.update(
{ CarTable.id eq cordsInBeing[HumanBeingTable.coordinates] }
) {
it[cool] = item.car.cool
}
HumanBeingTable.update(
{ HumanBeingTable.id eq id }
) {
it[owner_id] = item.ownerId
it[name] = item.name
it[creationDate] = item.creationDate
it[realHero] = item.realHero
it[impactSpeed] = item.impactSpeed
it[soundtrackName] = item.soundtrackName
it[minutesOfWaiting] = item.minutesOfWaiting
it[hasToothpick] = item.hasToothpick
it[weaponType] = item.weaponType
}
allGood = true
}
}
return allGood
}
fun delete(id: UUID): Boolean {
var allGood = true
try {
transaction {
HumanBeingTable.deleteWhere { HumanBeingTable.id eq id }
}
} catch (e: Exception) {
allGood = false
}
return allGood
}
}
@@ -1,15 +1,12 @@
package database.tables
import models.WeaponType
import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.javatime.date
import org.postgresql.util.PGobject
import kotlin.uuid.ExperimentalUuidApi
import org.jetbrains.exposed.v1.core.dao.id.java.UUIDTable
object HumanBeingTable : Table("humanBeing") {
@OptIn(ExperimentalUuidApi::class)
val id = uuid("id")
object HumanBeingTable : UUIDTable("humanBeing") {
val owner_id = reference("owner_id", UserTable.id)
val name = text("name")
val coordinates = reference("coordinates", CoordinatesTable.id)
@@ -21,21 +18,21 @@ object HumanBeingTable : Table("humanBeing") {
val minutesOfWaiting = float("minutes_of_waiting")
val car = reference("car", CarTable.id)
val weaponType = customEnumeration(
name = "weapon_type",
sql = "weapontype",
name = "weapon_type",
sql = "weapontype",
fromDb = { value ->
WeaponType.valueOf(
(value as PGobject).value!!
)
val strValue = when (value) {
is PGobject -> value.value!!
is String -> value
else -> error("Unexpected type: ${value::class}")
}
WeaponType.valueOf(strValue.uppercase())
},
toDb = { value ->
toDb = { value ->
PGobject().apply {
type = "weapontype"
this.value = value.name
type = "weapontype"
this.value = value.name.lowercase()
}
}
).nullable().default(null)
@OptIn(ExperimentalUuidApi::class)
override val primaryKey = PrimaryKey(id)
}
-179
View File
@@ -1,179 +0,0 @@
package file
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
import java.time.LocalDate
import java.time.format.DateTimeParseException
import java.util.Scanner
import java.util.UUID
private val logger = KotlinLogging.logger {}
/**
* Менеджер файлового хранилища коллекции.
*
* Читает данные с помощью [Scanner], записывает через [OutputStreamWriter].
* Формат хранения — CSV, одна строка = один объект [HumanBeing].
*
* Порядок полей в CSV:
* `id,name,coordX,coordY,creationDate,realHero,hasToothpick,
* impactSpeed,soundtrackName,minutesOfWaiting,weaponType,carCool`
*
* @property filePath путь к CSV-файлу, полученный из аргумента командной строки
*/
class FileManager(private val filePath: String) {
companion object {
private const val DELIMITER = ","
private const val HEADER =
"id,name,coordX,coordY,creationDate,realHero," +
"hasToothpick,impactSpeed,soundtrackName,minutesOfWaiting,weaponType,carCool"
}
/**
* Читает коллекцию из CSV-файла с помощью [Scanner].
*
* Пропускает заголовок и битые строки (с предупреждением в stderr).
* При отсутствии файла или проблемах с доступом возвращает пустой список.
*
* @return список корректно распарсенных объектов [HumanBeing]
*/
fun read(): List<HumanBeing> {
val file = File(filePath)
if (!file.exists()) {
logger.warn{"There's no '$filePath'. Collection will be empty."}
return emptyList()
}
if (!file.canRead()) {
logger.warn{"Permission denied: '$filePath'. Collection will be empty."}
return emptyList()
}
val result = mutableListOf<HumanBeing>()
var lineNumber = 0
try {
Scanner(file, Charsets.UTF_8).use { scanner ->
while (scanner.hasNextLine()) {
val line = scanner.nextLine().trim()
lineNumber++
// skip header and empty lines
if (lineNumber == 1 && line.startsWith("id")) continue
if (line.isBlank()) continue
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) {
logger.warn(e) {"File not found: ${e.message}"}
}
logger.info {"${result.size} elements loaded from'$filePath'"}
return result
}
/**
* Парсит одну CSV-строку в объект [HumanBeing].
*
* @param line строка CSV
* @param lineNumber номер строки (для сообщений об ошибках)
* @return объект [HumanBeing] или null при ошибке парсинга
*/
private fun parseLine(line: String, lineNumber: Int): HumanBeing? {
return try {
val parts = line.split(DELIMITER, limit = 12)
if (parts.size < 12) {
logger.warn {"Line $lineNumber: expected 12 fields, found ${parts.size}"}
return null
}
val id = UUID.fromString(parts[0].trim())
val name = parts[1].trim()
val coordX = parts[2].trim().toLong()
val coordY = parts[3].trim().toInt()
val creationDate = LocalDate.parse(parts[4].trim())
val realHero = parts[5].trim().toBoolean()
val hasToothpick = parts[6].trim().toBoolean()
val impactSpeed = parts[7].trim().toDouble()
val soundtrackName = parts[8].trim()
val minutesOfWaiting = parts[9].trim().toFloat()
val weaponType = parts[10].trim().let {
if (it.equals("null", ignoreCase = true) || it.isBlank()) null
else WeaponType.valueOf(it)
}
val carCool = parts[11].trim().toBoolean()
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: Exception) {
logger.warn(e) {"Line $lineNumber: ${e.message}"}
null
}
}
/**
* Записывает коллекцию в CSV-файл с помощью [OutputStreamWriter].
*
* @param items коллекция объектов [HumanBeing] для записи
* @return true если запись прошла успешно
*/
fun write(items: Collection<HumanBeing>, user: UUID): Boolean {
val file = File(filePath)
if (file.exists() && !file.canWrite()) {
logger.warn{"Permission denied: '$filePath'."}
return false
}
return try {
OutputStreamWriter(file.outputStream(), Charsets.UTF_8).use { writer ->
writer.write(HEADER)
writer.write("\n")
items.forEach { h ->
writer.write(toCSVLine(h))
writer.write("\n")
}
}
logger.info {"Collection saved to'$filePath' (${items.size} elements)."}
true
} catch (e: SecurityException) {
logger.warn(e) {"Permission denied: '$filePath'."}
false
} catch (e: Exception) {
logger.warn(e) {"An error occurred: '$filePath'."}
false
}
}
/**
* Сериализует объект [HumanBeing] в строку CSV.
*
* @param h объект для сериализации
* @return строка в формате CSV
*/
private fun toCSVLine(h: HumanBeing): String = listOf(
h.id, h.name, h.coordinates.x, h.coordinates.y, h.creationDate,
h.realHero, h.hasToothpick, h.impactSpeed, h.soundtrackName,
h.minutesOfWaiting, h.weaponType ?: "null", h.car.cool
).joinToString(DELIMITER)
}
@@ -11,8 +11,6 @@ import network.Response
* возвращает Response готовый для отправки клиенту.
*/
class CommandInvoker {
private val serverOnlyCommands = setOf("save", "sync-server")
private val commands = mutableMapOf<String, Command>()
private val history = ArrayDeque<String>()
@@ -21,9 +19,6 @@ class CommandInvoker {
}
fun execute(request: Request): Response {
if (request.commandName in serverOnlyCommands) {
return Response(false, "Команда '${request.commandName}' недоступна клиенту.")
}
val command = commands[request.commandName]
?: return Response(false, "Неизвестная команда: '${request.commandName}'")