initial commit2
This commit is contained in:
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,69 @@
|
||||
|
||||
import commands.AuthCommand
|
||||
import commands.ExecuteScriptCommand
|
||||
import commands.ExitCommand
|
||||
import commands.HelpCommand
|
||||
import commands.LogoutCommand
|
||||
import commands.SyncCommand
|
||||
import network.NetworkManager
|
||||
import input.IOManager
|
||||
import runner.CommandInvoker
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val host = if (args.isNotEmpty()) args[0] else "localhost"
|
||||
val port = if (args.size >= 2) args[1].toIntOrNull() ?: 8080 else 8080
|
||||
|
||||
val io = IOManager() // Name by Boris Bosenko
|
||||
val client = NetworkManager(host, port)
|
||||
val invoker = CommandInvoker(io, client)
|
||||
|
||||
// Shutdown hook
|
||||
Runtime.getRuntime().addShutdownHook(Thread {
|
||||
client.close()
|
||||
})
|
||||
|
||||
// do auth stuff
|
||||
listOf(
|
||||
HelpCommand(invoker),
|
||||
AuthCommand(networkManager = client),
|
||||
).forEach { invoker.register(it)}
|
||||
|
||||
while (client.getToken() == "no token") {
|
||||
val line = io.readLine(" ☭ ") ?: break
|
||||
if (line.isBlank()) continue
|
||||
invoker.execute(line)
|
||||
}
|
||||
|
||||
io.print("""
|
||||
|════════════════════════════════════════════
|
||||
|Lab6 Client — подключение к $host:$port
|
||||
|════════════════════════════════════════════
|
||||
""".trimMargin())
|
||||
|
||||
registerBasicCommands(invoker, client, io)
|
||||
runClientRepl(io, invoker)
|
||||
}
|
||||
|
||||
fun registerBasicCommands(invoker: CommandInvoker,client: NetworkManager, io: IOManager) {
|
||||
listOf(
|
||||
ExecuteScriptCommand(io),
|
||||
SyncCommand(invoker, client),
|
||||
LogoutCommand(client)
|
||||
).forEach { invoker.register(it)}
|
||||
}
|
||||
|
||||
fun runClientRepl(io: IOManager, invoker : CommandInvoker) {
|
||||
// sync commands w server
|
||||
invoker.execute("sync")
|
||||
|
||||
var isRunning = true
|
||||
invoker.register(ExitCommand {isRunning = false})
|
||||
|
||||
while (isRunning) {
|
||||
val line = io.readLine(" ☭ ") ?: break
|
||||
if (line.isBlank()) continue
|
||||
invoker.execute(line)
|
||||
}
|
||||
|
||||
io.print("До свидания!")
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package commands
|
||||
|
||||
import models.HumanBeing
|
||||
import network.AuthRequest
|
||||
import network.AuthResponse
|
||||
import network.AuthType
|
||||
import network.NetworkManager
|
||||
import java.security.MessageDigest
|
||||
|
||||
class AuthCommand(
|
||||
private val networkManager: NetworkManager,
|
||||
): ClientCommand {
|
||||
override val name: String = "auth"
|
||||
override val description: String = "вход или регистрация. auth login <username> <password> or auth register <username> <password>"
|
||||
override val args: List<CommandArgType> = listOf(CommandArgType.NONE)
|
||||
override val type: CommandType = CommandType.SIMPLE
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
if (args.isEmpty()) return "[Ошибка] Укажите действие, логин и пароль. Пример: auth login <username> <password>"
|
||||
if (args[1].isEmpty() || args[2].isEmpty()) return "[Ошибка] Укажите логин и пароль. Пример: auth login <username> <password>"
|
||||
|
||||
val res = if (args[0] == "login") {
|
||||
networkManager.sendRequest(AuthRequest(args[1], md5Hash(args[2]), AuthType.LOGIN))
|
||||
} else if (args[0] == "register") {
|
||||
networkManager.sendRequest(AuthRequest(args[1], md5Hash(args[2]), AuthType.REGISTER))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (res == null) return "[!] Сервер не отвечает."
|
||||
if (res.success) {
|
||||
networkManager.setToken(res.accessToken!!)
|
||||
networkManager.setUUID(res.id!!)
|
||||
}
|
||||
return res.message
|
||||
}
|
||||
|
||||
private fun md5Hash(input: String): String {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
val hashBytes = md.digest(input.toByteArray())
|
||||
return hashBytes.joinToString("") { String.format("%02x", it) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package commands
|
||||
|
||||
import models.HumanBeing
|
||||
|
||||
interface ClientCommand {
|
||||
val name: String
|
||||
val description: String
|
||||
val args: List<CommandArgType>
|
||||
val type: CommandType
|
||||
|
||||
fun execute(args: List<String>, humanBeing: HumanBeing? = null): String
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package commands
|
||||
|
||||
import input.IOManager
|
||||
import models.HumanBeing
|
||||
|
||||
class ExecuteScriptCommand(private val io: IOManager) : ClientCommand {
|
||||
override val name = "execute_script"
|
||||
override val description = "Выполняет скрипт из файла"
|
||||
override val args = listOf(CommandArgType.STRING)
|
||||
override val type = CommandType.SIMPLE
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
io.addScriptScanner(args[0])
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package commands
|
||||
|
||||
import models.HumanBeing
|
||||
|
||||
class ExitCommand(private val onExit: () -> Unit) : ClientCommand {
|
||||
override val name = "exit"
|
||||
override val description = "завершить клиент"
|
||||
override val args: List<CommandArgType> = listOf(CommandArgType.NONE)
|
||||
override val type = CommandType.SIMPLE
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
onExit()
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package commands
|
||||
|
||||
import models.HumanBeing
|
||||
import runner.CommandInvoker
|
||||
|
||||
class HelpCommand(private val invoker: CommandInvoker) : ClientCommand {
|
||||
override val name = "help"
|
||||
override val description = "вывести справку по доступным командам"
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
override val type = CommandType.SIMPLE
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String =
|
||||
invoker.getCommands().values
|
||||
.sortedBy { it.name }
|
||||
.joinToString("\n") { " ${it.name.padEnd(40)} — ${it.description}" }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package commands
|
||||
|
||||
import models.HumanBeing
|
||||
import network.NetworkManager
|
||||
|
||||
class LogoutCommand(private val networkManager: NetworkManager): ClientCommand {
|
||||
override val name: String = "logout"
|
||||
override val description: String = "выход из аккаунта"
|
||||
override val args: List<CommandArgType> = listOf(CommandArgType.NONE)
|
||||
override val type: CommandType = CommandType.SIMPLE
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
networkManager.clearToken()
|
||||
return "Пользователь вышел. Войдите заново"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package commands
|
||||
|
||||
import models.HumanBeing
|
||||
import network.Request
|
||||
import network.NetworkManager
|
||||
|
||||
class ServerCommand(
|
||||
override var name: String,
|
||||
override var description: String,
|
||||
override var args: List<CommandArgType>,
|
||||
override var type: CommandType,
|
||||
private val networkManager: NetworkManager,
|
||||
): ClientCommand {
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
val request = Request(
|
||||
commandName = this.name,
|
||||
args = args,
|
||||
humanBeing = humanBeing,
|
||||
jwt = networkManager.getToken()
|
||||
)
|
||||
val response = networkManager.sendRequest(request)
|
||||
return response?.message ?: "[!] Сервер не ответил"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package commands
|
||||
|
||||
import models.HumanBeing
|
||||
import network.NetworkManager
|
||||
import network.Request
|
||||
import runner.CommandInvoker
|
||||
|
||||
class SyncCommand (
|
||||
private val invoker: CommandInvoker,
|
||||
private val client: NetworkManager,
|
||||
) : ClientCommand {
|
||||
override val name = "sync"
|
||||
override val description = "получить команды с сервера"
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
override val type = CommandType.SIMPLE
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
|
||||
val initialRequest = Request(
|
||||
commandName = "sync",
|
||||
humanBeing = null,
|
||||
jwt = client.getToken(),
|
||||
)
|
||||
val res = client.sendRequest(initialRequest)
|
||||
|
||||
if (res == null) {
|
||||
return ("[!] Сервер недоступен. Попробуйте позже.")
|
||||
} else {
|
||||
res.commands?.forEach { serverCommand ->
|
||||
invoker.register(
|
||||
ServerCommand(
|
||||
name = serverCommand.name,
|
||||
description = serverCommand.description,
|
||||
type = serverCommand.type,
|
||||
args = serverCommand.args,
|
||||
networkManager = client
|
||||
)
|
||||
)
|
||||
}
|
||||
return ("| Подключен. | Введите 'help' для справки.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package input
|
||||
|
||||
import models.Car
|
||||
import models.Coordinates
|
||||
import models.HumanBeing
|
||||
import models.WeaponType
|
||||
import network.NetworkManager
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Интерактивный построитель объекта [HumanBeing].
|
||||
*
|
||||
* Читает поля по одному из [inputManager], показывает приглашения,
|
||||
* валидирует ввод и повторяет запрос при ошибке.
|
||||
*
|
||||
* Поля [HumanBeing.id] и [HumanBeing.creationDate] генерируются
|
||||
* автоматически и не запрашиваются у пользователя.
|
||||
*
|
||||
* @property inputManager источник ввода
|
||||
*/
|
||||
|
||||
class HumanBeingBuilder(
|
||||
private val inputManager: IOManager,
|
||||
private val networkManager: NetworkManager
|
||||
) {
|
||||
|
||||
/**
|
||||
* Запускает интерактивный ввод всех полей и возвращает готовый [HumanBeing].
|
||||
*
|
||||
* @throws IllegalStateException если EOF
|
||||
*/
|
||||
fun build(): HumanBeing {
|
||||
val name = readNonBlankString("Введите имя:")
|
||||
val coordinates = readCoordinates()
|
||||
val realHero = readBoolean("Герой? (true/false):")
|
||||
val hasToothpick = readBoolean("Есть зубочистка? (true/false):")
|
||||
val impactSpeed = readDouble("Введите скорость удара (impactSpeed):")
|
||||
val soundtrackName = readNonBlankString("Введите название саундтрека:")
|
||||
val minutesOfWaiting = readFloat("Введите минуты ожидания (minutesOfWaiting):")
|
||||
val weaponType = readNullableWeaponType()
|
||||
val car = readCar()
|
||||
|
||||
return HumanBeing(
|
||||
name = name, coordinates = coordinates, realHero = realHero,
|
||||
hasToothpick = hasToothpick, impactSpeed = impactSpeed,
|
||||
soundtrackName = soundtrackName, minutesOfWaiting = minutesOfWaiting,
|
||||
weaponType = weaponType, car = car, ownerId = networkManager.getUUID()
|
||||
)
|
||||
}
|
||||
|
||||
private fun readCoordinates(): Coordinates {
|
||||
val x = readLong("Введите координату X (тип Long):")
|
||||
val y = readInt("Введите координату Y (тип Int):")
|
||||
return Coordinates(x, y)
|
||||
}
|
||||
|
||||
private fun readCar(): Car {
|
||||
val cool = readBoolean("Крутой автомобиль ? (true/false):")
|
||||
return Car(cool)
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает непустую строку. Повторяет запрос при пустом вводе.
|
||||
*/
|
||||
private fun readNonBlankString(prompt: String): String {
|
||||
while (true) {
|
||||
val input = inputManager.readLine(" > $prompt")
|
||||
?: throw IllegalStateException("EOF")
|
||||
if (input.isNotBlank()) return input.trim()
|
||||
inputManager.print("[Ошибка] Строка не может быть пустой.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает Long. Повторяет запрос при некорректном вводе.
|
||||
*/
|
||||
private fun readLong(prompt: String): Long {
|
||||
while (true) {
|
||||
val input = inputManager.readLine(" > $prompt")
|
||||
?: throw IllegalStateException("EOF")
|
||||
try {
|
||||
return input.trim().toLong()
|
||||
} catch (e: NumberFormatException) {
|
||||
inputManager.print("[Ошибка] Ожидается целое число (Long). Попробуйте снова.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает Int. Повторяет запрос при некорректном вводе.
|
||||
*/
|
||||
private fun readInt(prompt: String): Int {
|
||||
while (true) {
|
||||
val input = inputManager.readLine(" > $prompt")
|
||||
?: throw IllegalStateException("EOF")
|
||||
try {
|
||||
return input.trim().toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
inputManager.print("[Ошибка] Ожидается целое число (Int). Попробуйте снова.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает Double. Повторяет запрос при некорректном вводе.
|
||||
*/
|
||||
private fun readDouble(prompt: String): Double {
|
||||
while (true) {
|
||||
val input = inputManager.readLine(" > $prompt")
|
||||
?: throw IllegalStateException("EOF")
|
||||
try {
|
||||
return input.trim().toDouble()
|
||||
} catch (e: NumberFormatException) {
|
||||
inputManager.print("[Ошибка] Ожидается число с плавающей точкой (Double). Попробуйте снова.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает Float. Повторяет запрос при некорректном вводе.
|
||||
*/
|
||||
private fun readFloat(prompt: String): Float {
|
||||
while (true) {
|
||||
val input = inputManager.readLine(" > $prompt")
|
||||
?: throw IllegalStateException("EOF")
|
||||
try {
|
||||
return input.trim().toFloat()
|
||||
} catch (e: NumberFormatException) {
|
||||
inputManager.print("[Ошибка] Ожидается число (Float). Попробуйте снова.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает Boolean (true/false). Повторяет запрос при некорректном вводе.
|
||||
*/
|
||||
private fun readBoolean(prompt: String): Boolean {
|
||||
while (true) {
|
||||
val input = inputManager.readLine(" > $prompt")
|
||||
?: throw IllegalStateException("EOF")
|
||||
when (input.trim().lowercase()) {
|
||||
"true" -> return true
|
||||
"false" -> return false
|
||||
else -> inputManager.print("[Ошибка] Ожидается 'true' или 'false'. Попробуйте снова.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает [WeaponType] или null (пустая строка).
|
||||
* Перед вводом выводит список допустимых констант.
|
||||
*/
|
||||
private fun readNullableWeaponType(): WeaponType? {
|
||||
val constants = WeaponType.entries.joinToString(", ")
|
||||
while (true) {
|
||||
val input = inputManager.readLine(" > Введите тип оружия (доступные: $constants) или оставьте пустым для null:")
|
||||
?: throw IllegalStateException("EOF")
|
||||
if (input.isBlank()) return null
|
||||
try {
|
||||
return WeaponType.valueOf(input.trim().uppercase())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
inputManager.print("[Ошибка] Неизвестный тип оружия '$input'. Допустимые значения: $constants")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выводит приглашение к вводу только в интерактивном режиме.
|
||||
* В режиме скрипта приглашения не выводятся.
|
||||
*/
|
||||
private fun printPrompt(message: String) {
|
||||
if (inputManager.isInteractive) inputManager.print(" > $message ")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package input
|
||||
|
||||
import org.jline.reader.EndOfFileException
|
||||
import org.jline.reader.LineReader
|
||||
import org.jline.reader.LineReaderBuilder
|
||||
import org.jline.reader.UserInterruptException
|
||||
import org.jline.reader.impl.history.DefaultHistory
|
||||
import org.jline.terminal.TerminalBuilder
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.ArrayDeque
|
||||
import java.util.Scanner
|
||||
|
||||
class IOManager {
|
||||
val isInteractive: Boolean = true
|
||||
|
||||
private val terminal = TerminalBuilder.builder()
|
||||
.system(true)
|
||||
.build()
|
||||
|
||||
private val history = DefaultHistory()
|
||||
|
||||
private val lineReader: LineReader = LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.history(history)
|
||||
// .completer(StringsCompleter(invoker.getCommandNames()))
|
||||
.option(LineReader.Option.HISTORY_IGNORE_DUPS, true)
|
||||
.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)
|
||||
.build()
|
||||
|
||||
private val scriptQueue = ArrayDeque<Scanner>()
|
||||
private val fileHistory = ArrayDeque<String>()
|
||||
|
||||
/**
|
||||
* Читает строку.
|
||||
* Если есть активный скрипт — читает из него.
|
||||
* Иначе — интерактивный ввод через JLine.
|
||||
*/
|
||||
fun readLine(prompt: String): String? {
|
||||
// Режим скрипта
|
||||
while (scriptQueue.isNotEmpty()) {
|
||||
val scanner = scriptQueue.peek()
|
||||
if (scanner.hasNextLine()) {
|
||||
return scanner.nextLine()
|
||||
}
|
||||
scanner.close()
|
||||
scriptQueue.pop()
|
||||
fileHistory.pop()
|
||||
}
|
||||
|
||||
return try {
|
||||
lineReader.readLine(prompt)
|
||||
} catch (e: UserInterruptException) {
|
||||
""
|
||||
} catch (e: EndOfFileException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun addScriptScanner(filePath: String) {
|
||||
if (fileHistory.contains(filePath)) {
|
||||
printErrConsole("Скрипт уже был выполнен: $filePath")
|
||||
return
|
||||
}
|
||||
try {
|
||||
fileHistory.push(filePath)
|
||||
scriptQueue.push(Scanner(File(filePath), Charsets.UTF_8))
|
||||
} catch (e: FileNotFoundException) {
|
||||
printErrConsole("Файл не найден: $filePath")
|
||||
}
|
||||
}
|
||||
|
||||
fun print(text: String) = println(text)
|
||||
|
||||
fun printErrConsole(text: String) = System.err.println(text)
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package network
|
||||
|
||||
import kotlinx.serialization.SerializationException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.PortUnreachableException
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.DatagramChannel
|
||||
import java.util.UUID
|
||||
|
||||
class NetworkManager(
|
||||
private val host: String,
|
||||
private val port: Int,
|
||||
private val bufferSize: Int = 65507,
|
||||
private val timeoutMS: Long = 3000,
|
||||
private val retryDelayMS: Long = 5000,
|
||||
private val maxRetries: Int = 3,
|
||||
) {
|
||||
|
||||
private var token: String = "no token"
|
||||
private var id: String = " no id"
|
||||
|
||||
private var channel: DatagramChannel = openChannel()
|
||||
|
||||
private fun openChannel(): DatagramChannel =
|
||||
DatagramChannel.open().also {
|
||||
it.configureBlocking(false)
|
||||
it.connect(InetSocketAddress(host, port))
|
||||
}
|
||||
|
||||
fun sendRequest(authRequest: AuthRequest): AuthResponse? {
|
||||
val requestBytes = AppJson.encodeToString(AuthRequest.serializer(), authRequest)
|
||||
.toByteArray(Charsets.UTF_8)
|
||||
|
||||
repeat(maxRetries) { attempt ->
|
||||
try {
|
||||
channel.write(ByteBuffer.wrap(requestBytes))
|
||||
val response = waitForAuthResponse()
|
||||
if (response != null) return response
|
||||
} catch (e: PortUnreachableException) {
|
||||
reconnect()
|
||||
} catch (e: Exception) {
|
||||
reconnect()
|
||||
}
|
||||
|
||||
println(
|
||||
"[Client] Сервер не отвечает " +
|
||||
"(попытка ${attempt + 1}/$maxRetries). " +
|
||||
"Повтор через ${retryDelayMS / 1000} сек..."
|
||||
)
|
||||
Thread.sleep(retryDelayMS)
|
||||
}
|
||||
|
||||
println("[Client] Сервер недоступен. Вход не выполнен.")
|
||||
return null
|
||||
}
|
||||
|
||||
fun sendRequest(request: Request): Response? {
|
||||
val requestBytes = AppJson.encodeToString(Request.serializer(), request)
|
||||
.toByteArray(Charsets.UTF_8)
|
||||
|
||||
repeat(maxRetries) { attempt ->
|
||||
try {
|
||||
channel.write(ByteBuffer.wrap(requestBytes))
|
||||
val response = waitForResponse()
|
||||
if (response != null) return response
|
||||
} catch (e: PortUnreachableException) {
|
||||
reconnect()
|
||||
} catch (e: Exception) {
|
||||
reconnect()
|
||||
}
|
||||
|
||||
println(
|
||||
"[Client] Сервер не отвечает " +
|
||||
"(попытка ${attempt + 1}/$maxRetries). " +
|
||||
"Повтор через ${retryDelayMS / 1000} сек..."
|
||||
)
|
||||
Thread.sleep(retryDelayMS)
|
||||
}
|
||||
|
||||
println("[Client] Сервер недоступен. Команда не выполнена.")
|
||||
return null
|
||||
}
|
||||
|
||||
private fun waitForResponse(): Response? {
|
||||
val buf = ByteBuffer.allocate(bufferSize)
|
||||
val deadline = System.currentTimeMillis() + timeoutMS
|
||||
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
try {
|
||||
buf.clear()
|
||||
if (channel.read(buf) > 0) {
|
||||
buf.flip()
|
||||
return AppJson.decodeFromString<Response>(
|
||||
Charsets.UTF_8.decode(buf).toString()
|
||||
)
|
||||
}
|
||||
} catch (e: PortUnreachableException) {
|
||||
return null
|
||||
} catch (e: SerializationException) {
|
||||
System.err.println("[Client] Некорректный ответ сервера: ${e.message}")
|
||||
return null
|
||||
}
|
||||
Thread.sleep(50)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun waitForAuthResponse(): AuthResponse? {
|
||||
val buf = ByteBuffer.allocate(bufferSize)
|
||||
val deadline = System.currentTimeMillis() + timeoutMS
|
||||
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
try {
|
||||
buf.clear()
|
||||
if (channel.read(buf) > 0) {
|
||||
buf.flip()
|
||||
return AppJson.decodeFromString<AuthResponse>(
|
||||
Charsets.UTF_8.decode(buf).toString()
|
||||
)
|
||||
}
|
||||
} catch (e: PortUnreachableException) {
|
||||
return null
|
||||
} catch (e: SerializationException) {
|
||||
System.err.println("[Client] Некорректный ответ сервера: ${e.message}")
|
||||
return null
|
||||
}
|
||||
Thread.sleep(50)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun reconnect() {
|
||||
try { channel.close() } catch (_: Exception) {}
|
||||
channel = openChannel()
|
||||
}
|
||||
|
||||
fun close() {
|
||||
try { channel.close() } catch (_: Exception) {}
|
||||
}
|
||||
|
||||
fun setToken(newToken: String) {
|
||||
token = newToken
|
||||
}
|
||||
|
||||
fun getToken(): String = token
|
||||
|
||||
fun clearToken() {
|
||||
token = "no token"
|
||||
}
|
||||
|
||||
fun setUUID(newToken: String) {
|
||||
id = newToken
|
||||
}
|
||||
|
||||
fun getUUID(): UUID {
|
||||
return UUID.fromString(id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package runner
|
||||
|
||||
import commands.ClientCommand
|
||||
import commands.CommandType
|
||||
import input.HumanBeingBuilder
|
||||
import input.IOManager
|
||||
import network.NetworkManager
|
||||
|
||||
class CommandInvoker(
|
||||
private val io: IOManager,
|
||||
private val networkManager: NetworkManager
|
||||
) {
|
||||
private val commands = mutableMapOf<String, ClientCommand>()
|
||||
private val history = ArrayDeque<String>(12)
|
||||
|
||||
fun register(command: ClientCommand) {
|
||||
commands[command.name] = command
|
||||
}
|
||||
|
||||
fun execute(input: String) {
|
||||
val parts = input.trim().split(" ")
|
||||
val name = parts[0].lowercase().trim()
|
||||
val args = parts.drop(1)
|
||||
|
||||
val command = commands[name]
|
||||
if (command == null) {
|
||||
io.print("Неизвестная команда: '$name'. Введите 'help' для справки.")
|
||||
return
|
||||
}
|
||||
|
||||
addToHistory(name)
|
||||
try {
|
||||
if (command.type == CommandType.MODEL) {
|
||||
val model = HumanBeingBuilder(io, networkManager).build()
|
||||
val res = command.execute(args, model)
|
||||
io.print(res)
|
||||
} else {
|
||||
val res = command.execute(args)
|
||||
io.print(res)
|
||||
}
|
||||
} catch (e: IllegalStateException) {
|
||||
io.print("[Ошибка выполнения] ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun getHistory(): List<String> = history.toList()
|
||||
|
||||
fun getCommands(): Map<String, ClientCommand> = commands.toMap()
|
||||
|
||||
private fun addToHistory(name: String) {
|
||||
if (history.size >= 12) history.removeFirst()
|
||||
history.addLast(name)
|
||||
}
|
||||
|
||||
fun getCommandNames(): List<String> = commands.keys.toList()
|
||||
}
|
||||
Reference in New Issue
Block a user