diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/chatServer.cpp | 14 | ||||
| -rw-r--r-- | src/commandHandler.cpp | 58 | ||||
| -rw-r--r-- | src/commandHandler.hpp | 67 | ||||
| -rw-r--r-- | src/commands.cpp | 46 | ||||
| -rw-r--r-- | src/commands.hpp | 1 | ||||
| -rw-r--r-- | src/inputManager.cpp | 54 | ||||
| -rw-r--r-- | src/inputManager.hpp | 13 |
7 files changed, 251 insertions, 2 deletions
diff --git a/src/chatServer.cpp b/src/chatServer.cpp index 8e9a3ab..56caf4b 100644 --- a/src/chatServer.cpp +++ b/src/chatServer.cpp @@ -1,5 +1,8 @@ #include "chatServer.hpp" #include "chatManager.hpp" +#include "commandHandler.hpp" +#include "commands.hpp" +#include "inputManager.hpp" #include "log.hpp" #include "server.hpp" #include "sessionManager.hpp" @@ -23,6 +26,8 @@ void CChatServer::cleanup() { // m_sessionManagerThread.join(); g_pSessionManager.reset(); g_pServer.reset(); + g_pCommandHandler.reset(); + g_pInputManager.reset(); } void CChatServer::initManagers() { @@ -33,15 +38,20 @@ void CChatServer::initManagers() { g_pSessionManager = std::make_shared<CSessionManager>(); log(LOG, "Creating ChatManager"); g_pChatManager = std::make_unique<CChatManager>(); + log(LOG, "Creating InputManager"); + g_pInputManager = std::make_unique<CInputManager>(); } void CChatServer::start() { initManagers(); + log(LOG, "Creating CommandHandler"); + g_pCommandHandler = std::make_unique<CCommandHandler>(); + registerCommands(); + // will await while waiting for accept() to return a new client m_sessionManagerThread = std::jthread(&CSessionManager::run, g_pSessionManager.get()); m_sessionManagerThread.detach(); - while (true) { - } + g_pInputManager->run(); } diff --git a/src/commandHandler.cpp b/src/commandHandler.cpp new file mode 100644 index 0000000..12b51dc --- /dev/null +++ b/src/commandHandler.cpp @@ -0,0 +1,58 @@ +#include "commandHandler.hpp" +#include "log.hpp" +#include <algorithm> + +CCommandHandler::CCommandHandler() { + log(LOG, "CommandHandler: initialized"); +} + +CCommandHandler::~CCommandHandler() { + log(SYS, "CommandHandler: bye"); + m_vCommands.clear(); +} + +bool CCommandHandler::isCommand(const std::string &str) const { + return !str.empty() && str.front() == '/'; +}; + +bool CCommandHandler::validCommand(const std::string &command) const { + return std::ranges::any_of(m_vCommands, [&command](const SCommand &cmd) { + return cmd.name == command; + }); +} + +bool CCommandHandler::registerCommand(const SCommand &command) { + if (command.name.empty() || !command.exe || !command.parser) + return false; + + m_vCommands.push_back(command); + return true; +} + +const CCommandHandler::SCommand *CCommandHandler::getCommand(const std::string &command) const { + auto it = std::ranges::find_if(m_vCommands, [&command](const SCommand &cmd) { return cmd.name == command; }); + + if (it != m_vCommands.end()) + return &*it; + + return nullptr; +} + +CCommandHandler::SResult CCommandHandler::exeCommand(const SCommand &command, const std::string &args) const { + const auto [parsed, good] = command.parser(args); + if (!good) + try { + return {.result = std::any_cast<std::string>(parsed), .good = false}; + } catch (const std::bad_any_cast &) { + return {.result = "", .good = false}; + } + + return command.exe(parsed); +} + +CCommandHandler::SResult CCommandHandler::newCommand(const std::string &command, const std::string &args) const { + if (!validCommand(command)) + return {.result = "Invalid command", .good = false}; + + return exeCommand(*getCommand(command), args); +} diff --git a/src/commandHandler.hpp b/src/commandHandler.hpp new file mode 100644 index 0000000..78ab2e5 --- /dev/null +++ b/src/commandHandler.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include <any> +#include <functional> +#include <memory> +#include <string> +#include <vector> + +class CCommandHandler { + public: + CCommandHandler(); + ~CCommandHandler(); + + struct SResult { + std::string result; + bool good = false; + }; + + struct SParseResult { + std::any data; + bool good = false; + }; + + struct SCommand { + std::string name; + std::function<SParseResult(const std::string &args)> parser; + std::function<SResult(const std::any &parsed)> exe; + }; + + bool isCommand(const std::string &str) const; + bool validCommand(const std::string &command) const; + // return false if command struct is invalid + bool registerCommand(const SCommand &command); + SResult newCommand(const std::string &command, const std::string &args) const; + + template <typename T> + struct SMakeCommandArgs { + std::string name; + std::function<std::pair<T, bool>(const std::string &args)> parser; + std::function<SResult(const T &parsed)> exe; + }; + + template <typename T> + static SCommand makeCommand(SMakeCommandArgs<T> args) { + return { + .name = std::move(args.name), + .parser = [parser = std::move(args.parser)](const std::string &args) -> SParseResult { + auto [data, good] = parser(args); + return {.data = std::any(std::move(data)), .good = good}; + }, + .exe = [exe = std::move(args.exe)](const std::any &parsed) -> SResult { + try { + return exe(std::any_cast<const T &>(parsed)); + } catch (const std::bad_any_cast &) { + return {.result = "Invalid data type", .good = false}; + } + } + }; + } + + private: + const SCommand *getCommand(const std::string &command) const; + SResult exeCommand(const SCommand &command, const std::string &args) const; + + std::vector<SCommand> m_vCommands; +}; +inline std::unique_ptr<CCommandHandler> g_pCommandHandler; diff --git a/src/commands.cpp b/src/commands.cpp new file mode 100644 index 0000000..1db5d43 --- /dev/null +++ b/src/commands.cpp @@ -0,0 +1,46 @@ +#include "commands.hpp" +#include "commandHandler.hpp" +#include "sessionManager.hpp" + +struct SKickArgs { + std::string target; + std::string reason; +}; + +void registerCommands() { + g_pCommandHandler->registerCommand({.name = "raw", .parser = [](const std::string &args) -> CCommandHandler::SParseResult { return {.data = args, .good = true}; }, .exe = [](const std::any &parsed) -> CCommandHandler::SResult { + const std::string &data = std::any_cast<const std::string &>(parsed); + g_pSessionManager->broadcast(data); + return {.result = "Broadcasted: " + data, .good = true}; }}); + + g_pCommandHandler->registerCommand(CCommandHandler::makeCommand<SKickArgs>({.name = "kick", .parser = [](const std::string &args) -> std::pair<SKickArgs, bool> { + size_t pos = args.find(' '); + std::string target = args; + std::string reason = "No reason specified"; + + if (pos != std::string::npos) { + target = args.substr(0, pos); + reason = args.substr(pos + 1); + if (reason.empty()) + reason = "Kicked: No reason specified"; + } + + if (target.empty()) + return {SKickArgs{}, false}; + + return { + SKickArgs{.target=target, .reason=reason}, true}; }, .exe = [](const SKickArgs &data) -> CCommandHandler::SResult { + const auto session = g_pSessionManager->getByName(data.target); + if (!session) + return {.result = "User '" + data.target + "' not found", .good = false}; + + g_pSessionManager->kick(session, true, data.reason); + return {.result = "Kicked '" + data.target + "': " + data.reason, .good = true}; }})); + + g_pCommandHandler->registerCommand({.name = "list", .parser = [](const std::string &args) -> CCommandHandler::SParseResult { return {.good = true}; }, .exe = [](const std::any &parsed) -> CCommandHandler::SResult { + std::string result = "Clients:\n"; + for (const auto &session : g_pSessionManager->getSessions()) + result += session->getName() + "\t" + session->getIp() + "\n"; + + return {.result = result, .good = true}; }}); +} diff --git a/src/commands.hpp b/src/commands.hpp new file mode 100644 index 0000000..b49b6dc --- /dev/null +++ b/src/commands.hpp @@ -0,0 +1 @@ +void registerCommands(); diff --git a/src/inputManager.cpp b/src/inputManager.cpp new file mode 100644 index 0000000..57b7b06 --- /dev/null +++ b/src/inputManager.cpp @@ -0,0 +1,54 @@ +#include "inputManager.hpp" +#include "chatManager.hpp" +#include "commandHandler.hpp" +#include "log.hpp" +#include <iostream> + +CInputManager::CInputManager() { + log(LOG, "InputManager: initialized"); +} + +CInputManager::~CInputManager() { + log(SYS, "InputManager: bye"); +} + +void CInputManager::run() { + inputLoop(); +} + +static void handleCommand(std::string &input) { + input = input.substr(1); + + std::string command; + std::string args; + + size_t spacePos = input.find(' '); + if (spacePos == std::string::npos) { + command = input; + args = ""; + } else { + command = input.substr(0, spacePos); + // skip extra spaces + size_t startPos = input.find_first_not_of(' ', spacePos); + if (startPos != std::string::npos) + args = input.substr(startPos); + } + + args.empty() ? log(TRACE, "Spawning Command: {}", command) : log(TRACE, "Spawning Command: {}, Args: {}", command, args); + const auto exe = g_pCommandHandler->newCommand(command, args); + exe.good ? exe.result.empty() ? log(LOG, "Command {} succeeded", command) : log(LOG, "Command {} succeeded: {}", command, exe.result) : exe.result.empty() ? log(ERR, "Command {} failed", command) + : log(ERR, "Command {} failed: {}", command, exe.result); +}; + +void CInputManager::inputLoop() { + while (true) { + std::string input; + std::print("> "); + std::getline(std::cin, input); + + if (input.empty()) + continue; + + g_pCommandHandler->isCommand(input) ? handleCommand(input) : g_pChatManager->broadcastMessage({.msg = input, .username = "Server"}); + } +} diff --git a/src/inputManager.hpp b/src/inputManager.hpp new file mode 100644 index 0000000..ef75b52 --- /dev/null +++ b/src/inputManager.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include <memory> + +class CInputManager { + public: + CInputManager(); + ~CInputManager(); + + void run(); + void inputLoop(); +}; +inline std::unique_ptr<CInputManager> g_pInputManager; |
