summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/chatServer.cpp14
-rw-r--r--src/commandHandler.cpp58
-rw-r--r--src/commandHandler.hpp67
-rw-r--r--src/commands.cpp46
-rw-r--r--src/commands.hpp1
-rw-r--r--src/inputManager.cpp54
-rw-r--r--src/inputManager.hpp13
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;