race condition fix
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,3 @@
|
|||||||
Manifest-Version: 1.0
|
Manifest-Version: 1.0
|
||||||
Main-Class: MainKt
|
Main-Class: MainKt
|
||||||
Multi-Release: true
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
|||||||
�F
|
ンF澎
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -1,2 +1,2 @@
|
|||||||
30
|
43
|
||||||
0
|
0
|
||||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,3 @@
|
|||||||
Manifest-Version: 1.0
|
Manifest-Version: 1.0
|
||||||
Main-Class: MainKt
|
Main-Class: MainKt
|
||||||
Multi-Release: true
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import auth.JWTManager
|
import auth.JWTManager
|
||||||
import collection.CollectionManager
|
|
||||||
import commands.*
|
|
||||||
import database.CacheInvalidator
|
|
||||||
import database.DatabaseManager
|
import database.DatabaseManager
|
||||||
import database.Repository
|
import database.Repository
|
||||||
|
import commands.*
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import network.NetworkManager
|
import network.NetworkManager
|
||||||
import runner.CommandInvoker
|
import runner.CommandInvoker
|
||||||
@@ -24,28 +22,16 @@ fun main(args: Array<String>) {
|
|||||||
database.start()
|
database.start()
|
||||||
val repository = Repository(jwtManager)
|
val repository = Repository(jwtManager)
|
||||||
|
|
||||||
val manager = CollectionManager()
|
|
||||||
manager.loadFromFile(repository.read())
|
|
||||||
|
|
||||||
// Синхронизация кэша между серверами через PG NOTIFY
|
|
||||||
val cacheInvalidator = CacheInvalidator(database.dataSource) {
|
|
||||||
manager.loadFromFile(repository.read())
|
|
||||||
}
|
|
||||||
cacheInvalidator.start()
|
|
||||||
|
|
||||||
val invoker = CommandInvoker(jwtManager)
|
val invoker = CommandInvoker(jwtManager)
|
||||||
|
|
||||||
registerServerCommands(invoker, manager, repository)
|
registerServerCommands(invoker, repository)
|
||||||
|
|
||||||
// (Ctrl+C or kill)
|
|
||||||
val server = NetworkManager(port = port, invoker = invoker, repository = repository)
|
val server = NetworkManager(port = port, invoker = invoker, repository = repository)
|
||||||
Runtime.getRuntime().addShutdownHook(Thread {
|
Runtime.getRuntime().addShutdownHook(Thread {
|
||||||
logger.info{ "Exit..." }
|
logger.info{ "Exit..." }
|
||||||
cacheInvalidator.stop()
|
|
||||||
server.stop()
|
server.stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Server-side console
|
|
||||||
Thread {
|
Thread {
|
||||||
val scanner = java.util.Scanner(System.`in`)
|
val scanner = java.util.Scanner(System.`in`)
|
||||||
logger.info{ "[Server-console] Server-side commands: exit" }
|
logger.info{ "[Server-console] Server-side commands: exit" }
|
||||||
@@ -69,23 +55,22 @@ private fun loadDotEnv() {
|
|||||||
|
|
||||||
fun registerServerCommands(
|
fun registerServerCommands(
|
||||||
invoker: CommandInvoker,
|
invoker: CommandInvoker,
|
||||||
manager: CollectionManager,
|
|
||||||
repository: Repository
|
repository: Repository
|
||||||
) {
|
) {
|
||||||
listOf(
|
listOf(
|
||||||
InfoCommand(manager),
|
InfoCommand(repository),
|
||||||
ShowCommand(manager),
|
ShowCommand(repository),
|
||||||
AddCommand(manager, repository),
|
AddCommand(repository),
|
||||||
UpdateCommand(manager, repository),
|
UpdateCommand(repository),
|
||||||
RemoveByIdCommand(manager, repository),
|
RemoveByIdCommand(repository),
|
||||||
ClearCommand(manager, repository),
|
ClearCommand(repository),
|
||||||
AddIfMaxCommand(manager, repository),
|
AddIfMaxCommand(repository),
|
||||||
AddIfMinCommand(manager, repository),
|
AddIfMinCommand(repository),
|
||||||
HistoryCommand(invoker),
|
HistoryCommand(invoker),
|
||||||
SyncCommand(invoker),
|
SyncCommand(invoker),
|
||||||
SumOfMinutesCommand(manager),
|
SumOfMinutesCommand(repository),
|
||||||
MinByNameCommand(manager),
|
MinByNameCommand(repository),
|
||||||
PrintDescendingMinutesCommand(manager),
|
PrintDescendingMinutesCommand(repository),
|
||||||
).forEach { invoker.register(it)}
|
).forEach { invoker.register(it)}
|
||||||
logger.info{ " [startup] Registered commands: ${invoker.getCommands().keys}" }
|
logger.info{ " [startup] Registered commands: ${invoker.getCommands().keys}" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
package collection
|
|
||||||
|
|
||||||
import models.HumanBeing
|
|
||||||
import mu.KotlinLogging
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.util.Collections
|
|
||||||
import java.util.TreeSet
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
|
||||||
|
|
||||||
class CollectionManager {
|
|
||||||
|
|
||||||
private val collection: MutableSet<HumanBeing> =
|
|
||||||
Collections.synchronizedSortedSet(TreeSet())
|
|
||||||
|
|
||||||
val initDate: LocalDate = LocalDate.now()
|
|
||||||
|
|
||||||
fun add(humanBeing: HumanBeing): Boolean {
|
|
||||||
logger.info { "[ADD] Element created ${humanBeing.id}" }
|
|
||||||
return collection.add(humanBeing)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(id: UUID, updated: HumanBeing): Boolean {
|
|
||||||
synchronized(collection) {
|
|
||||||
val old = getById(id) ?: return false
|
|
||||||
collection.remove(old)
|
|
||||||
val replaced = updated.copy(id = old.id, creationDate = old.creationDate)
|
|
||||||
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] Element deleted ${target.id}" }
|
|
||||||
return collection.remove(target)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clear() {
|
|
||||||
collection.clear()
|
|
||||||
logger.info { "[CLEAR] Collection cleared" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAll(): TreeSet<HumanBeing> {
|
|
||||||
synchronized(collection) {
|
|
||||||
return TreeSet(collection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getById(id: UUID): HumanBeing? {
|
|
||||||
synchronized(collection) {
|
|
||||||
return collection.firstOrNull { it.id == id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun size(): Int = collection.size
|
|
||||||
|
|
||||||
fun isEmpty(): Boolean = collection.isEmpty()
|
|
||||||
|
|
||||||
fun getMax(): HumanBeing? {
|
|
||||||
synchronized(collection) {
|
|
||||||
return collection.maxWithOrNull(Comparator.naturalOrder())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getMin(): HumanBeing? {
|
|
||||||
synchronized(collection) {
|
|
||||||
return collection.minWithOrNull(Comparator.naturalOrder())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sumOfMinutesOfWaiting(): Double {
|
|
||||||
synchronized(collection) {
|
|
||||||
return collection.sumOf { it.minutesOfWaiting.toDouble() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun minByName(): HumanBeing? {
|
|
||||||
synchronized(collection) {
|
|
||||||
return collection.minByOrNull { it.name }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getMinutesOfWaitingDescending(): List<Float> {
|
|
||||||
synchronized(collection) {
|
|
||||||
return collection.map { it.minutesOfWaiting }.sortedDescending()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadFromFile(items: List<HumanBeing>) {
|
|
||||||
synchronized(collection) {
|
|
||||||
collection.clear()
|
|
||||||
collection.addAll(items)
|
|
||||||
}
|
|
||||||
logger.info { "[LOAD] loaded ${items.size} elements" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getInfo(): String =
|
|
||||||
"""
|
|
||||||
|Тип коллекции : ${collection::class.simpleName}
|
|
||||||
|Тип элементов : ${HumanBeing::class.simpleName}
|
|
||||||
|Дата инициал. : $initDate
|
|
||||||
|Кол-во элемен.: ${collection.size}
|
|
||||||
""".trimMargin()
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
|
||||||
import database.Repository
|
import database.Repository
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -10,7 +9,6 @@ import java.util.UUID
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class AddCommand(
|
class AddCommand(
|
||||||
@Contextual private val manager: CollectionManager,
|
|
||||||
@Contextual private val repository: Repository
|
@Contextual private val repository: Repository
|
||||||
) : Command {
|
) : Command {
|
||||||
override val name = "add"
|
override val name = "add"
|
||||||
@@ -23,11 +21,6 @@ class AddCommand(
|
|||||||
val secured = humanBeing.copy(ownerId = UUID.fromString(userId))
|
val secured = humanBeing.copy(ownerId = UUID.fromString(userId))
|
||||||
secured.id = repository.write(secured)
|
secured.id = repository.write(secured)
|
||||||
secured.id ?: return Response(false, "Не удалось добавить элемент")
|
secured.id ?: return Response(false, "Не удалось добавить элемент")
|
||||||
|
return Response(true, "Элемент добавлен: ${secured.name} [${secured.id}]")
|
||||||
return if (manager.add(secured))
|
|
||||||
Response(true, "Элемент добавлен: ${secured.name} [${secured.id}]")
|
|
||||||
|
|
||||||
else
|
|
||||||
Response(false, "Элемент уже существует в коллекции.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
|
||||||
import database.Repository
|
import database.Repository
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -10,7 +9,6 @@ import java.util.UUID
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class AddIfMaxCommand(
|
class AddIfMaxCommand(
|
||||||
@Contextual val manager: CollectionManager,
|
|
||||||
@Contextual private val repository: Repository
|
@Contextual private val repository: Repository
|
||||||
) : Command {
|
) : Command {
|
||||||
override val name = "add_if_max"
|
override val name = "add_if_max"
|
||||||
@@ -21,11 +19,10 @@ class AddIfMaxCommand(
|
|||||||
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
|
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
|
||||||
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
||||||
val secured = humanBeing.copy(ownerId = UUID.fromString(userId))
|
val secured = humanBeing.copy(ownerId = UUID.fromString(userId))
|
||||||
val max = manager.getMax()
|
val max = repository.getMax()
|
||||||
return if (max == null || secured > max) {
|
return if (max == null || secured > max) {
|
||||||
secured.id = repository.write(secured)
|
secured.id = repository.write(secured)
|
||||||
secured.id ?: return Response(false, "Не удалось добавить элемент")
|
secured.id ?: return Response(false, "Не удалось добавить элемент")
|
||||||
manager.add(secured)
|
|
||||||
Response(true, "Элемент добавлен (превышает максимум).")
|
Response(true, "Элемент добавлен (превышает максимум).")
|
||||||
} else {
|
} else {
|
||||||
Response(false, "Элемент не добавлен: не превышает текущий максимум (${max.name}).")
|
Response(false, "Элемент не добавлен: не превышает текущий максимум (${max.name}).")
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
|
||||||
import database.Repository
|
import database.Repository
|
||||||
import models.HumanBeing
|
import models.HumanBeing
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -10,7 +9,6 @@ import java.util.UUID
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class AddIfMinCommand(
|
class AddIfMinCommand(
|
||||||
@Contextual private val manager: CollectionManager,
|
|
||||||
@Contextual private val repository: Repository
|
@Contextual private val repository: Repository
|
||||||
) : Command {
|
) : Command {
|
||||||
override val name = "add_if_min"
|
override val name = "add_if_min"
|
||||||
@@ -21,11 +19,10 @@ class AddIfMinCommand(
|
|||||||
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
|
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
|
||||||
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
||||||
val secured = humanBeing.copy(ownerId = UUID.fromString(userId))
|
val secured = humanBeing.copy(ownerId = UUID.fromString(userId))
|
||||||
val min = manager.getMin()
|
val min = repository.getMin()
|
||||||
return if (min == null || secured < min) {
|
return if (min == null || secured < min) {
|
||||||
secured.id = repository.write(secured)
|
secured.id = repository.write(secured)
|
||||||
secured.id ?: return Response(false, "Не удалось добавить элемент")
|
secured.id ?: return Response(false, "Не удалось добавить элемент")
|
||||||
manager.add(secured)
|
|
||||||
Response(true, "Элемент добавлен (меньше минимума).")
|
Response(true, "Элемент добавлен (меньше минимума).")
|
||||||
} else {
|
} else {
|
||||||
Response(false, "Элемент не добавлен: не меньше текущего минимума (${min.name}).")
|
Response(false, "Элемент не добавлен: не меньше текущего минимума (${min.name}).")
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
|
||||||
import database.Repository
|
import database.Repository
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -10,7 +9,6 @@ import java.util.UUID
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ClearCommand(
|
class ClearCommand(
|
||||||
@Contextual private val manager: CollectionManager,
|
|
||||||
@Contextual private var repository: Repository
|
@Contextual private var repository: Repository
|
||||||
) : Command {
|
) : Command {
|
||||||
override val name = "clear"
|
override val name = "clear"
|
||||||
@@ -23,9 +21,6 @@ class ClearCommand(
|
|||||||
val ownerId = UUID.fromString(userId)
|
val ownerId = UUID.fromString(userId)
|
||||||
|
|
||||||
val deletedCount = repository.deleteByOwner(ownerId)
|
val deletedCount = repository.deleteByOwner(ownerId)
|
||||||
val toRemove = manager.getAll().filter { it.ownerId == ownerId }
|
|
||||||
toRemove.forEach { manager.removeById(it.id!!) }
|
|
||||||
|
|
||||||
return Response(true, "Удалено объектов: $deletedCount.")
|
return Response(true, "Удалено объектов: $deletedCount.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
import database.Repository
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import models.HumanBeing
|
import models.HumanBeing
|
||||||
import network.Response
|
import network.Response
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class InfoCommand(@Contextual private val manager: CollectionManager) : Command {
|
class InfoCommand(@Contextual private val repository: Repository) : Command {
|
||||||
override val name = "info"
|
override val name = "info"
|
||||||
override val description = "вывести информацию о коллекции"
|
override val description = "вывести информацию о коллекции"
|
||||||
override val type = CommandType.SIMPLE
|
override val type = CommandType.SIMPLE
|
||||||
override val args = listOf(CommandArgType.NONE)
|
override val args = listOf(CommandArgType.NONE)
|
||||||
|
|
||||||
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response =
|
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response =
|
||||||
Response(true, manager.getInfo())
|
Response(true, repository.getInfo())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
import database.Repository
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import models.HumanBeing
|
import models.HumanBeing
|
||||||
import network.Response
|
import network.Response
|
||||||
|
|
||||||
/**
|
|
||||||
* Выводит любой элемент с минимальным значением поля [HumanBeing.name].
|
|
||||||
*/
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class MinByNameCommand(@Contextual private val manager: CollectionManager) : Command {
|
class MinByNameCommand(@Contextual private val repository: Repository) : Command {
|
||||||
override val name = "min_by_name"
|
override val name = "min_by_name"
|
||||||
override val description = "вывести элемент с минимальным именем"
|
override val description = "вывести элемент с минимальным именем"
|
||||||
override val type = CommandType.SIMPLE
|
override val type = CommandType.SIMPLE
|
||||||
override val args = listOf(CommandArgType.NONE)
|
override val args = listOf(CommandArgType.NONE)
|
||||||
|
|
||||||
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response =
|
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response =
|
||||||
Response(true, manager.minByName()?.toString() ?: "Коллекция пуста.")
|
Response(true, repository.minByName()?.toString() ?: "Коллекция пуста.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
import database.Repository
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import models.HumanBeing
|
import models.HumanBeing
|
||||||
import network.Response
|
import network.Response
|
||||||
|
|
||||||
/**
|
|
||||||
* Выводит значения поля [models.HumanBeing.minutesOfWaiting] в порядке убывания.
|
|
||||||
*/
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class PrintDescendingMinutesCommand(@Contextual private val manager: CollectionManager) : Command {
|
class PrintDescendingMinutesCommand(@Contextual private val repository: Repository) : Command {
|
||||||
override val name = "print_field_descending_minutes_of_waiting"
|
override val name = "print_field_descending_minutes_of_waiting"
|
||||||
override val description = "вывести minutesOfWaiting в порядке убывания"
|
override val description = "вывести minutesOfWaiting в порядке убывания"
|
||||||
override val type = CommandType.SIMPLE
|
override val type = CommandType.SIMPLE
|
||||||
override val args = listOf(CommandArgType.NONE)
|
override val args = listOf(CommandArgType.NONE)
|
||||||
|
|
||||||
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
|
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
|
||||||
val values = manager.getMinutesOfWaitingDescending()
|
val values = repository.getMinutesOfWaitingDescending()
|
||||||
return if (values.isEmpty()) Response(true, "Коллекция пуста.")
|
return if (values.isEmpty()) Response(true, "Коллекция пуста.")
|
||||||
else Response(true, values.joinToString("\n"))
|
else Response(true, values.joinToString("\n"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
|
||||||
import database.Repository
|
import database.Repository
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -10,7 +9,6 @@ import network.Response
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class RemoveByIdCommand(
|
class RemoveByIdCommand(
|
||||||
@Contextual private val manager: CollectionManager,
|
|
||||||
@Contextual private val repository: Repository
|
@Contextual private val repository: Repository
|
||||||
) : Command {
|
) : Command {
|
||||||
override val name = "remove_by_id"
|
override val name = "remove_by_id"
|
||||||
@@ -27,17 +25,16 @@ class RemoveByIdCommand(
|
|||||||
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
|
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
|
||||||
}
|
}
|
||||||
|
|
||||||
val existing = manager.getById(id)
|
val existing = repository.getById(id)
|
||||||
?: return Response(false, "Элемент не найден.")
|
?: return Response(false, "Элемент не найден.")
|
||||||
|
|
||||||
if (existing.ownerId.toString() != userId)
|
if (existing.ownerId.toString() != userId)
|
||||||
return Response(false, "Нет прав: объект принадлежит другому пользователю.")
|
return Response(false, "Нет прав: объект принадлежит другому пользователю.")
|
||||||
|
|
||||||
if (repository.delete(id)) {
|
return if (repository.delete(id)) {
|
||||||
manager.removeById(id)
|
Response(true, "Элемент удалён.")
|
||||||
return Response(true, "Элемент удалён.")
|
|
||||||
} else {
|
} else {
|
||||||
return Response(false, "[Ошибка] Элемент с id '$id' не найден и не был удалён.")
|
Response(false, "[Ошибка] Элемент с id '$id' не найден и не был удалён.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
import database.Repository
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import models.HumanBeing
|
import models.HumanBeing
|
||||||
import network.Response
|
import network.Response
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ShowCommand(@Contextual private val manager: CollectionManager) : Command {
|
class ShowCommand(@Contextual private val repository: Repository) : Command {
|
||||||
override val name = "show"
|
override val name = "show"
|
||||||
override val description = "вывести все элементы коллекции"
|
override val description = "вывести все элементы коллекции"
|
||||||
override val type = CommandType.SIMPLE
|
override val type = CommandType.SIMPLE
|
||||||
override val args = listOf(CommandArgType.NONE)
|
override val args = listOf(CommandArgType.NONE)
|
||||||
|
|
||||||
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
|
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
|
||||||
if (manager.isEmpty()) return Response(true, "Коллекция пуста.")
|
val all = repository.getAll()
|
||||||
return Response(true, manager.getAll().joinToString("\n") { it.toString() })
|
if (all.isEmpty()) return Response(true, "Коллекция пуста.")
|
||||||
|
return Response(true, all.joinToString("\n") { it.toString() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
import database.Repository
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import models.HumanBeing
|
import models.HumanBeing
|
||||||
import network.Response
|
import network.Response
|
||||||
|
|
||||||
/**
|
|
||||||
* Выводит сумму значений поля [models.HumanBeing.minutesOfWaiting].
|
|
||||||
*/
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class SumOfMinutesCommand(@Contextual private val manager: CollectionManager) : Command {
|
class SumOfMinutesCommand(@Contextual private val repository: Repository) : Command {
|
||||||
override val name = "sum_of_minutes_of_waiting"
|
override val name = "sum_of_minutes_of_waiting"
|
||||||
override val description = "вывести сумму minutesOfWaiting всех элементов"
|
override val description = "вывести сумму minutesOfWaiting всех элементов"
|
||||||
override val type = CommandType.SIMPLE
|
override val type = CommandType.SIMPLE
|
||||||
override val args = listOf(CommandArgType.NONE)
|
override val args = listOf(CommandArgType.NONE)
|
||||||
|
|
||||||
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response =
|
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response =
|
||||||
Response(true, "Сумма minutesOfWaiting: ${manager.sumOfMinutesOfWaiting()}")
|
Response(true, "Сумма minutesOfWaiting: ${repository.sumOfMinutesOfWaiting()}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import collection.CollectionManager
|
|
||||||
import database.Repository
|
import database.Repository
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -10,7 +9,6 @@ import java.util.UUID
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class UpdateCommand(
|
class UpdateCommand(
|
||||||
@Contextual private val manager: CollectionManager,
|
|
||||||
@Contextual private val repository: Repository
|
@Contextual private val repository: Repository
|
||||||
) : Command {
|
) : Command {
|
||||||
override val name = "update"
|
override val name = "update"
|
||||||
@@ -28,17 +26,16 @@ class UpdateCommand(
|
|||||||
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
|
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
|
||||||
}
|
}
|
||||||
|
|
||||||
val existing = manager.getById(id)
|
val existing = repository.getById(id)
|
||||||
?: return Response(false, "Элемент не найден.")
|
?: return Response(false, "Элемент не найден.")
|
||||||
|
|
||||||
if (existing.ownerId.toString() != userId)
|
if (existing.ownerId.toString() != userId)
|
||||||
return Response(false, "Нет прав: объект принадлежит другому пользователю.")
|
return Response(false, "Нет прав: объект принадлежит другому пользователю.")
|
||||||
|
|
||||||
if (repository.update(id, humanBeing)) {
|
return if (repository.update(id, humanBeing)) {
|
||||||
manager.update(id, humanBeing)
|
Response(true, "Элемент обновлён.")
|
||||||
return Response(true, "Элемент обновлён.")
|
|
||||||
} else {
|
} else {
|
||||||
return Response(false, "[Ошибка] Элемент не найден и не был обновлён.")
|
Response(false, "[Ошибка] Элемент не найден и не был обновлён.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
|
||||||
import org.postgresql.PGConnection
|
|
||||||
import javax.sql.DataSource
|
|
||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
|
||||||
|
|
||||||
class CacheInvalidator(
|
|
||||||
private val dataSource: DataSource,
|
|
||||||
private val onInvalidated: () -> Unit
|
|
||||||
) {
|
|
||||||
@Volatile
|
|
||||||
private var running = false
|
|
||||||
private var thread: Thread? = null
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
running = true
|
|
||||||
thread = Thread {
|
|
||||||
while (running) {
|
|
||||||
try {
|
|
||||||
dataSource.connection.use { conn ->
|
|
||||||
conn.autoCommit = true
|
|
||||||
val pgConn = conn.unwrap(PGConnection::class.java)
|
|
||||||
|
|
||||||
conn.createStatement().use { stmt ->
|
|
||||||
stmt.execute("LISTEN cache_changed")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info { "Listening for cache changes via PG NOTIFY..." }
|
|
||||||
|
|
||||||
while (running && !conn.isClosed) {
|
|
||||||
val notifications = pgConn.notifications
|
|
||||||
if (notifications != null && notifications.isNotEmpty()) {
|
|
||||||
logger.debug { "Cache invalidation received" }
|
|
||||||
onInvalidated()
|
|
||||||
}
|
|
||||||
Thread.sleep(500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (!running) break
|
|
||||||
logger.warn(e) { "Cache listener error, reconnecting in 5s..." }
|
|
||||||
Thread.sleep(5000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.also { it.isDaemon = true }.apply {
|
|
||||||
name = "pg-notify-listener"
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stop() {
|
|
||||||
running = false
|
|
||||||
thread?.interrupt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,7 @@ import models.HumanBeing
|
|||||||
import org.jetbrains.exposed.v1.core.*
|
import org.jetbrains.exposed.v1.core.*
|
||||||
import org.jetbrains.exposed.v1.jdbc.*
|
import org.jetbrains.exposed.v1.jdbc.*
|
||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
|
import java.time.LocalDate
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
@@ -19,48 +20,54 @@ private val logger = KotlinLogging.logger {}
|
|||||||
class Repository(
|
class Repository(
|
||||||
private val jwtManager: JWTManager,
|
private val jwtManager: JWTManager,
|
||||||
) {
|
) {
|
||||||
|
val initDate: LocalDate = LocalDate.now()
|
||||||
|
|
||||||
fun read(): List<HumanBeing> {
|
fun getAll(): List<HumanBeing> {
|
||||||
val beings = mutableListOf<HumanBeing>()
|
val beings = mutableListOf<HumanBeing>()
|
||||||
transaction {
|
transaction {
|
||||||
val humans = HumanBeingTable
|
val humans = HumanBeingTable.selectAll()
|
||||||
.selectAll()
|
|
||||||
|
|
||||||
for (human in humans) {
|
for (human in humans) {
|
||||||
logger.trace { "read. $human" }
|
logger.trace { "getAll. $human" }
|
||||||
val cord = CoordinatesTable
|
val cord = CoordinatesTable
|
||||||
.select(CoordinatesTable.x, CoordinatesTable.y)
|
.select(CoordinatesTable.x, CoordinatesTable.y)
|
||||||
.where(CoordinatesTable.id eq human[HumanBeingTable.coordinates])
|
.where(CoordinatesTable.id eq human[HumanBeingTable.coordinates])
|
||||||
.single()
|
.single()
|
||||||
|
|
||||||
val car = CarTable
|
val car = CarTable
|
||||||
.select(CarTable.cool)
|
.select(CarTable.cool)
|
||||||
.where(CarTable.id eq human[HumanBeingTable.car])
|
.where(CarTable.id eq human[HumanBeingTable.car])
|
||||||
.single()
|
.single()
|
||||||
|
beings.add(toHumanBeing(human, cord, car))
|
||||||
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
|
return beings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getById(id: UUID): HumanBeing? = getAll().firstOrNull { it.id == id }
|
||||||
|
|
||||||
|
fun getMax(): HumanBeing? = getAll().maxWithOrNull(Comparator.naturalOrder())
|
||||||
|
|
||||||
|
fun getMin(): HumanBeing? = getAll().minWithOrNull(Comparator.naturalOrder())
|
||||||
|
|
||||||
|
fun sumOfMinutesOfWaiting(): Double =
|
||||||
|
getAll().sumOf { it.minutesOfWaiting.toDouble() }
|
||||||
|
|
||||||
|
fun minByName(): HumanBeing? = getAll().minByOrNull { it.name }
|
||||||
|
|
||||||
|
fun getMinutesOfWaitingDescending(): List<Float> =
|
||||||
|
getAll().map { it.minutesOfWaiting }.sortedDescending()
|
||||||
|
|
||||||
|
fun size(): Int = getAll().size
|
||||||
|
|
||||||
|
fun isEmpty(): Boolean = getAll().isEmpty()
|
||||||
|
|
||||||
|
fun getInfo(): String =
|
||||||
|
"""
|
||||||
|
|Тип коллекции : Реляционная БД (PostgreSQL)
|
||||||
|
|Тип элементов : ${HumanBeing::class.simpleName}
|
||||||
|
|Дата инициал. : $initDate
|
||||||
|
|Кол-во элемен.: ${size()}
|
||||||
|
""".trimMargin()
|
||||||
|
|
||||||
fun write(item: HumanBeing): UUID? {
|
fun write(item: HumanBeing): UUID? {
|
||||||
var humanId: UUID? = null
|
var humanId: UUID? = null
|
||||||
try {
|
try {
|
||||||
@@ -69,11 +76,9 @@ class Repository(
|
|||||||
it[x] = item.coordinates.x
|
it[x] = item.coordinates.x
|
||||||
it[y] = item.coordinates.y
|
it[y] = item.coordinates.y
|
||||||
}
|
}
|
||||||
|
|
||||||
val cars = CarTable.insertAndGetId {
|
val cars = CarTable.insertAndGetId {
|
||||||
it[cool] = item.car.cool
|
it[cool] = item.car.cool
|
||||||
}
|
}
|
||||||
|
|
||||||
humanId = HumanBeingTable.insertAndGetId {
|
humanId = HumanBeingTable.insertAndGetId {
|
||||||
it[owner_id] = item.ownerId
|
it[owner_id] = item.ownerId
|
||||||
it[name] = item.name
|
it[name] = item.name
|
||||||
@@ -87,9 +92,7 @@ class Repository(
|
|||||||
it[car] = cars
|
it[car] = cars
|
||||||
it[weaponType] = item.weaponType
|
it[weaponType] = item.weaponType
|
||||||
}.value
|
}.value
|
||||||
exec("NOTIFY cache_changed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.error(e) { "write failed. Here what's going on: \n $e \n " }
|
logger.error(e) { "write failed. Here what's going on: \n $e \n " }
|
||||||
return humanId
|
return humanId
|
||||||
@@ -137,7 +140,6 @@ class Repository(
|
|||||||
it[hasToothpick] = item.hasToothpick
|
it[hasToothpick] = item.hasToothpick
|
||||||
it[weaponType] = item.weaponType
|
it[weaponType] = item.weaponType
|
||||||
}
|
}
|
||||||
exec("NOTIFY cache_changed")
|
|
||||||
allGood = true
|
allGood = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +159,6 @@ class Repository(
|
|||||||
HumanBeingTable.deleteWhere { HumanBeingTable.id eq id }
|
HumanBeingTable.deleteWhere { HumanBeingTable.id eq id }
|
||||||
CoordinatesTable.deleteWhere { CoordinatesTable.id eq refs[HumanBeingTable.coordinates] }
|
CoordinatesTable.deleteWhere { CoordinatesTable.id eq refs[HumanBeingTable.coordinates] }
|
||||||
CarTable.deleteWhere { CarTable.id eq refs[HumanBeingTable.car] }
|
CarTable.deleteWhere { CarTable.id eq refs[HumanBeingTable.car] }
|
||||||
exec("NOTIFY cache_changed")
|
|
||||||
} else {
|
} else {
|
||||||
allGood = false
|
allGood = false
|
||||||
}
|
}
|
||||||
@@ -221,8 +222,26 @@ class Repository(
|
|||||||
deletedCount = HumanBeingTable.deleteWhere { HumanBeingTable.owner_id eq ownerId }
|
deletedCount = HumanBeingTable.deleteWhere { HumanBeingTable.owner_id eq ownerId }
|
||||||
CoordinatesTable.deleteWhere { CoordinatesTable.id inList coordIds }
|
CoordinatesTable.deleteWhere { CoordinatesTable.id inList coordIds }
|
||||||
CarTable.deleteWhere { CarTable.id inList carIds }
|
CarTable.deleteWhere { CarTable.id inList carIds }
|
||||||
exec("NOTIFY cache_changed")
|
|
||||||
}
|
}
|
||||||
return deletedCount
|
return deletedCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun toHumanBeing(
|
||||||
|
human: ResultRow,
|
||||||
|
cord: ResultRow,
|
||||||
|
car: ResultRow
|
||||||
|
): HumanBeing = HumanBeing(
|
||||||
|
name = human[HumanBeingTable.name],
|
||||||
|
ownerId = human[HumanBeingTable.owner_id].value,
|
||||||
|
coordinates = Coordinates(cord[CoordinatesTable.x]!!, cord[CoordinatesTable.y]!!),
|
||||||
|
car = Car(car[CarTable.cool]!!),
|
||||||
|
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]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user