From 5864a119391a4163022247eb972f29e575d91bb3 Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Fri, 22 Aug 2025 03:12:26 +0400 Subject: [PATCH 1/9] Some external commands refactor [skip ci] --- src/network/protocols/command_manager.cpp | 225 +++--------------- src/network/protocols/command_manager.hpp | 176 ++------------ src/network/protocols/server_lobby.cpp | 4 +- .../command_manager/abstract_resource.hpp | 40 ++++ src/utils/command_manager/auth_resource.cpp | 51 ++++ src/utils/command_manager/auth_resource.hpp | 38 +++ src/utils/command_manager/command.cpp | 49 ++++ src/utils/command_manager/command.hpp | 111 +++++++++ src/utils/command_manager/context.cpp | 89 +++++++ src/utils/command_manager/context.hpp | 74 ++++++ src/utils/command_manager/file_resource.cpp | 78 ++++++ src/utils/command_manager/file_resource.hpp | 47 ++++ src/utils/command_manager/text_resource.cpp | 33 +++ src/utils/command_manager/text_resource.hpp | 47 ++++ 14 files changed, 711 insertions(+), 351 deletions(-) create mode 100644 src/utils/command_manager/abstract_resource.hpp create mode 100644 src/utils/command_manager/auth_resource.cpp create mode 100644 src/utils/command_manager/auth_resource.hpp create mode 100644 src/utils/command_manager/command.cpp create mode 100644 src/utils/command_manager/command.hpp create mode 100644 src/utils/command_manager/context.cpp create mode 100644 src/utils/command_manager/context.hpp create mode 100644 src/utils/command_manager/file_resource.cpp create mode 100644 src/utils/command_manager/file_resource.hpp create mode 100644 src/utils/command_manager/text_resource.cpp create mode 100644 src/utils/command_manager/text_resource.hpp diff --git a/src/network/protocols/command_manager.cpp b/src/network/protocols/command_manager.cpp index 66ff144be67..880dbdc183e 100644 --- a/src/network/protocols/command_manager.cpp +++ b/src/network/protocols/command_manager.cpp @@ -21,7 +21,6 @@ #include "addons/addon.hpp" #include "io/file_manager.hpp" #include "modes/soccer_world.hpp" -#include "network/crypto.hpp" #include "network/database_connector.hpp" #include "network/event.hpp" #include "network/game_setup.hpp" @@ -62,6 +61,8 @@ // TODO: kimden: should decorators use acting_peer? +using Fn = std::function; + namespace { static const std::string g_addon_prefix = "addon_"; @@ -198,93 +199,6 @@ EnumExtendedReader CommandManager::permission_reader({ }); // ======================================================================== - -CommandManager::FileResource::FileResource(std::string file_name, uint64_t interval) -{ - m_file_name = std::move(file_name); - m_interval = interval; - m_contents = ""; - m_last_invoked = 0; - read(); -} // FileResource::FileResource -// ======================================================================== - -void CommandManager::FileResource::read() -{ - if (m_file_name.empty()) // in case it is not properly initialized - return; - // idk what to do with absolute or relative paths - const std::string& path = /*ServerConfig::getConfigDirectory() + "/" + */m_file_name; - std::ifstream message(FileUtils::getPortableReadingPath(path)); - std::string answer; - if (message.is_open()) - { - for (std::string line; std::getline(message, line); ) - { - answer += line; - answer.push_back('\n'); - } - if (!answer.empty()) - answer.pop_back(); - } - m_contents = answer; - m_last_invoked = StkTime::getMonoTimeMs(); -} // FileResource::read -// ======================================================================== - -std::string CommandManager::FileResource::get() -{ - uint64_t current_time = StkTime::getMonoTimeMs(); - if (m_interval == 0 || current_time < m_interval + m_last_invoked) - return m_contents; - read(); - return m_contents; -} // FileResource::get -// ======================================================================== - -CommandManager::AuthResource::AuthResource(std::string secret, std::string server, - std::string link_format): - m_secret(secret), m_server(server), m_link_format(link_format) -{ - -} // AuthResource::AuthResource -// ======================================================================== - -std::string CommandManager::AuthResource::get(const std::string& username, int online_id) const -{ -#ifdef ENABLE_CRYPTO_OPENSSL - std::string header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; - uint64_t timestamp = StkTime::getTimeSinceEpoch(); - std::string payload = "{\"sub\":\"" + username + "/" + std::to_string(online_id) + "\","; - payload += "\"iat\":\"" + std::to_string(timestamp) + "\","; - payload += "\"iss\":\"" + m_server + "\"}"; - header = Crypto::base64url(StringUtils::toUInt8Vector(header)); - payload = Crypto::base64url(StringUtils::toUInt8Vector(payload)); - std::string message = header + "." + payload; - std::string signature = Crypto::base64url(Crypto::hmac_sha256_array(m_secret, message)); - std::string token = message + "." + signature; - std::string response = StringUtils::insertValues(m_link_format, token.c_str()); - return response; -#else - return "This command is currently only supported for OpenSSL"; -#endif -} // AuthResource::get -// ======================================================================== - -CommandManager::Command::Command(std::string name, - void (CommandManager::*f)(Context& context), - int permissions, - int mode_scope, - int state_scope): - m_name(name), m_action(f), m_permissions(permissions), - m_mode_scope(mode_scope), m_state_scope(state_scope), - m_omit_name(false) -{ - // Handling players who are allowed to run for anyone in any case - m_permissions |= UU_OTHERS_COMMANDS; -} // Command::Command(5) -// ======================================================================== - const SetTypoFixer& CommandManager::getFixer(TypoFixerType type) { switch (type) @@ -343,9 +257,9 @@ void CommandManager::initCommandsInfo() continue; // here the commands go std::string name = ""; - std::string text = ""; // for text-command - std::string file = ""; // for file-command - uint64_t interval = 0; // for file-command + // std::string text = ""; // for text-command + // std::string file = ""; // for file-command + // uint64_t interval = 0; // for file-command std::string usage = ""; std::string permissions_s = "UP_EVERYONE"; std::string mode_scope_s = "MS_DEFAULT"; @@ -357,9 +271,9 @@ void CommandManager::initCommandsInfo() std::string permissions_str = ""; std::string description = ""; std::string aliases = ""; - std::string secret = ""; // for auth-command - std::string link_format = ""; // for auth-command - std::string server = ""; // for auth-command + // std::string secret = ""; // for auth-command + // std::string link_format = ""; // for auth-command + // std::string server = ""; // for auth-command // Name is read before enabled/disabled property, because we want // to disable commands in "default" config that are present in @@ -422,23 +336,30 @@ void CommandManager::initCommandsInfo() else if (node_name == "text-command") { c = addChildCommand(command, name, &CommandManager::process_text, permissions, mode_scope, state_scope); - node->get("text", &text); - addTextResponse(c->getFullName(), text); + TextResource resource; + resource.fromXmlNode(node); + addTextResponse(c->getFullName(), resource); } else if (node_name == "file-command") { c = addChildCommand(command, name, &CommandManager::process_file, permissions, mode_scope, state_scope); - node->get("file", &file); - node->get("interval", &interval); - addFileResource(c->getFullName(), file, interval); + FileResource resource; + resource.fromXmlNode(node); + addFileResource(c->getFullName(), resource); } + // else if (node_name == "map-file-command") + // { + // c = addChildCommand(command, name, &CommandManager::process_file, permissions, mode_scope, state_scope); + // MapFileResource resource; + // resource.fromXmlNode(node); + // addMapFileResource(c->getFullName(), resource); + // } else if (node_name == "auth-command") { c = addChildCommand(command, name, &CommandManager::process_auth, permissions, mode_scope, state_scope); - node->get("secret", &secret); - node->get("server", &server); - node->get("link-format", &link_format); - addAuthResource(name, secret, server, link_format); + AuthResource resource; + resource.fromXmlNode(node); + addAuthResource(name, resource); } c->m_description = CommandDescription(usage, permissions_str, description); m_all_commands.emplace_back(c); @@ -463,13 +384,12 @@ void CommandManager::initCommandsInfo() void CommandManager::initCommands() { - using CM = CommandManager; auto& mp = m_full_name_to_command; m_root_command = std::make_shared("", &CM::special); initCommandsInfo(); - auto applyFunctionIfPossible = [&](std::string&& name, void (CM::*f)(Context& context)) { + auto applyFunctionIfPossible = [&](std::string&& name, Fn f) { auto it = mp.find(name); if (it == mp.end()) return; @@ -478,7 +398,7 @@ void CommandManager::initCommands() if (!command) return; - command->changeFunction(f); + command->changeFunction(std::move(f)); }; // special permissions according to ServerConfig options std::shared_ptr kick_command = mp["kick"].lock(); @@ -625,15 +545,15 @@ void CommandManager::initCommands() applyFunctionIfPossible("liststkaddon", &CM::special); applyFunctionIfPossible("listlocaladdon", &CM::special); - addTextResponse("description", getSettings()->getMotd()); - addTextResponse("moreinfo", getSettings()->getHelpMessage()); + addTextResponse("description", { getSettings()->getMotd() }); + addTextResponse("moreinfo", { getSettings()->getHelpMessage() }); std::string version = Version::version(); std::string branch = Version::branch(); if (!branch.empty()) version += ", branch " + branch; - addTextResponse("version", version); + addTextResponse("version", { version }); addTextResponse("clear", std::string(30, '\n')); // m_votables.emplace("replay", 1.0); @@ -1024,7 +944,7 @@ void CommandManager::execute(std::shared_ptr command, Context& context) context.m_command = command; try { - (this->*(command->m_action))(context); + (*(command->m_action))(context); } catch (std::exception& ex) { @@ -1078,7 +998,7 @@ void CommandManager::process_text(Context& context) "Error: a text command %s is defined without text", command->getFullName().c_str()); else - response = it->second; + response = it->second.get(); context.say(response); } // process_text // ======================================================================== @@ -1096,7 +1016,7 @@ void CommandManager::process_file(Context& context) else response = it->second.get(); context.say(response); -} // process_text +} // process_file // ======================================================================== void CommandManager::process_auth(Context& context) @@ -4104,7 +4024,7 @@ void CommandManager::onStartSelection() update(); } // onStartSelection // ======================================================================== -std::shared_ptr CommandManager::addChildCommand(std::shared_ptr target, +std::shared_ptr CommandManager::addChildCommand(std::shared_ptr target, std::string name, void (CommandManager::*f)(Context& context), int permissions, int mode_scope, int state_scope) { @@ -4121,85 +4041,6 @@ std::shared_ptr CommandManager::addChildCommand(std::sh } // addChildCommand // ======================================================================== -std::shared_ptr CommandManager::Context::peer() -{ - if (m_peer.expired()) - throw std::logic_error("Peer is expired"); - - auto peer = m_peer.lock(); - if (!peer) - throw std::logic_error("Peer is invalid"); - - return peer; -} // peer -//----------------------------------------------------------------------------- - -std::shared_ptr CommandManager::Context::peerMaybeNull() -{ - // if (m_peer.expired()) - // throw std::logic_error("Peer is expired"); - - auto peer = m_peer.lock(); - return peer; -} // peerMaybeNull -//----------------------------------------------------------------------------- - -std::shared_ptr CommandManager::Context::actingPeer() -{ - if (m_target_peer.expired()) - throw std::logic_error("Target peer is expired"); - - auto acting_peer = m_target_peer.lock(); - if (!acting_peer) - throw std::logic_error("Target peer is invalid"); - - return acting_peer; -} // actingPeer -//----------------------------------------------------------------------------- - -std::shared_ptr CommandManager::Context::actingPeerMaybeNull() -{ - // if (m_target_peer.expired()) - // throw std::logic_error("Target peer is expired"); - - auto acting_peer = m_target_peer.lock(); - return acting_peer; -} // actingPeerMaybeNull -//----------------------------------------------------------------------------- - -std::shared_ptr CommandManager::Context::command() -{ - if (m_command.expired()) - throw std::logic_error("Command is expired"); - - auto command = m_command.lock(); - if (!command) - throw std::logic_error("Command is invalid"); - - return command; -} // command -//----------------------------------------------------------------------------- - -void CommandManager::Context::say(const std::string& s) -{ - if (m_peer.expired()) - throw std::logic_error("Context::say: Peer has expired"); - - auto peer = m_peer.lock(); - Comm::sendStringToPeer(peer, s); -} // say -//----------------------------------------------------------------------------- - -void CommandManager::Command::changePermissions(int permissions, - int mode_scope, int state_scope) -{ - // Handling players who are allowed to run for anyone in any case - m_permissions = permissions | UU_OTHERS_COMMANDS; - m_mode_scope = mode_scope; - m_state_scope = state_scope; -} // changePermissions -// ======================================================================== - std::string CommandManager::getAddonPreferredType() const { int mode = getLobby()->getGameMode(); diff --git a/src/network/protocols/command_manager.hpp b/src/network/protocols/command_manager.hpp index 7504d403278..89c3b7800c7 100644 --- a/src/network/protocols/command_manager.hpp +++ b/src/network/protocols/command_manager.hpp @@ -40,6 +40,11 @@ #include "network/protocols/command_voting.hpp" #include "network/protocols/command_permissions.hpp" +#include "utils/command_manager/auth_resource.hpp" +#include "utils/command_manager/command.hpp" +#include "utils/command_manager/context.hpp" +#include "utils/command_manager/file_resource.hpp" +#include "utils/command_manager/text_resource.hpp" #include "utils/enum_extended_reader.hpp" #include "utils/lobby_context.hpp" #include "utils/set_typo_fixer.hpp" @@ -55,47 +60,7 @@ class STKPeer; class CommandManager: public LobbyContextComponent { -public: - enum ModeScope: int - { - MS_DEFAULT = 1, - MS_SOCCER_TOURNAMENT = 2 - // add more powers of two if needed - }; - - enum StateScope: int - { - SS_LOBBY = 1, - SS_INGAME = 2, - SS_ALWAYS = SS_LOBBY | SS_INGAME - }; - private: - struct FileResource - { - std::string m_file_name; - uint64_t m_interval; - uint64_t m_last_invoked; - std::string m_contents; - - FileResource(std::string file_name = "", uint64_t interval = 0); - - void read(); - - std::string get(); - }; - - struct AuthResource - { - std::string m_secret; - std::string m_server; - std::string m_link_format; - - AuthResource(std::string secret = "", std::string server = "", - std::string link_format = ""); - - std::string get(const std::string& username, int online_id) const; - }; static EnumExtendedReader permission_reader; static EnumExtendedReader mode_scope_reader; @@ -106,115 +71,6 @@ class CommandManager: public LobbyContextComponent template void add_to_queue(int x, int mask, bool to_front, std::string& s) const; - struct Command; - - struct Context - { - ServerLobby* m_lobby; - - Event* m_event; - - std::weak_ptr m_peer; - - std::weak_ptr m_target_peer; - - std::vector m_argv; - - std::string m_cmd; - - std::weak_ptr m_command; - - int m_user_permissions; - - int m_acting_user_permissions; - - bool m_voting; - - Context(ServerLobby* lobby, Event* event, std::shared_ptr peer): - m_lobby(lobby), - m_event(event), m_peer(peer), m_target_peer(peer), m_argv(), - m_cmd(""), m_user_permissions(0), m_acting_user_permissions(0), m_voting(false) {} - - Context(ServerLobby* lobby, Event* event, std::shared_ptr peer, - std::vector& argv, std::string& cmd, - int user_permissions, int acting_user_permissions, bool voting): - m_lobby(lobby), - m_event(event), m_peer(peer), m_target_peer(peer), m_argv(argv), - m_cmd(cmd), m_user_permissions(user_permissions), - m_acting_user_permissions(acting_user_permissions), m_voting(voting) {} - - std::shared_ptr peer(); - std::shared_ptr peerMaybeNull(); - std::shared_ptr actingPeer(); - std::shared_ptr actingPeerMaybeNull(); - std::shared_ptr command(); - - void say(const std::string& s); - }; - - struct CommandDescription - { - std::string m_usage; - std::string m_permissions; - std::string m_description; - CommandDescription(std::string usage = "", std::string permissions = "", - std::string description = ""): m_usage(usage), - m_permissions(permissions), m_description(description) {} - - std::string getUsage() const { return "Usage: " + m_usage; } - - std::string getHelp() const - { - return "Usage: " + m_usage - + "\nAvailable to: " + m_permissions - + "\n" + m_description; - } - }; - - struct Command - { - std::string m_name; - - std::string m_prefix_name; - - void (CommandManager::*m_action)(Context& context); - - int m_permissions; - - int m_mode_scope; - - int m_state_scope; - - bool m_omit_name; - - CommandDescription m_description; - - std::weak_ptr m_parent; - - std::map> m_name_to_subcommand; - - std::vector> m_subcommands; - - SetTypoFixer m_stf_subcommand_names; - - Command() {} - - Command(std::string name, - void (CommandManager::*f)(Context& context), - int permissions = UP_EVERYONE, - int mode_scope = MS_DEFAULT, int state_scope = SS_ALWAYS); - - void changeFunction(void (CommandManager::*f)(Context& context)) - { m_action = f; } - void changePermissions(int permissions = UP_EVERYONE, - int mode_scope = MS_DEFAULT, - int state_scope = SS_ALWAYS); - - std::string getUsage() const { return m_description.getUsage(); } - std::string getHelp() const { return m_description.getHelp(); } - std::string getFullName() const { return m_prefix_name; } - }; - private: std::vector> m_all_commands; @@ -225,7 +81,7 @@ class CommandManager: public LobbyContextComponent std::multiset m_users; - std::map m_text_response; + std::map m_text_response; std::map m_file_resources; @@ -384,15 +240,21 @@ class CommandManager: public LobbyContextComponent void handleCommand(Event* event, std::shared_ptr peer); - template - void addTextResponse(std::string key, T&& value) - { m_text_response[key] = value; } + // template + // void addTextResponse(std::string key, T&& value) + // { m_text_response[key] = value; } + + void addTextResponse(std::string key, const TextResource& resource) + { m_text_response[key] = resource; } + + void addTextResponse(std::string key, TextResource&& resource) + { m_text_response[key] = std::move(resource); } - void addFileResource(std::string key, std::string file, uint64_t interval) - { m_file_resources[key] = FileResource(file, interval); } + void addFileResource(std::string key, const FileResource& resource) + { m_file_resources[key] = resource; } - void addAuthResource(std::string key, std::string secret, std::string server, std::string link_format) - { m_auth_resources[key] = AuthResource(secret, server, link_format); } + void addAuthResource(std::string key, const AuthResource& resource) + { m_auth_resources[key] = resource; } void addUser(std::string& s); diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 3f6bf333e24..ff36ea5cd44 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -4501,8 +4501,8 @@ int ServerLobby::getCurrentStateScope() || state > RESULT_DISPLAY) return 0; if (state == WAITING_FOR_START_GAME) - return CommandManager::StateScope::SS_LOBBY; - return CommandManager::StateScope::SS_INGAME; + return StateScope::SS_LOBBY; + return StateScope::SS_INGAME; } // getCurrentStateScope //----------------------------------------------------------------------------- diff --git a/src/utils/command_manager/abstract_resource.hpp b/src/utils/command_manager/abstract_resource.hpp new file mode 100644 index 00000000000..e5a80725038 --- /dev/null +++ b/src/utils/command_manager/abstract_resource.hpp @@ -0,0 +1,40 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef ABSTRACT_RESOURCE_HPP +#define ABSTRACT_RESOURCE_HPP + +#include +#include "io/xml_node.hpp" + +/** + * AbstractResource represents a generic externally produced resource + * (that is, typically not produced inside the game, or produced inside the game, + * but further processed elsewhere). + */ +class AbstractResource +{ +public: + + virtual void fromXmlNode(const XMLNode* node) = 0; + + // virtual std::string process(Context& context) = 0; +}; + + +#endif // ABSTRACT_RESOURCE_HPP \ No newline at end of file diff --git a/src/utils/command_manager/auth_resource.cpp b/src/utils/command_manager/auth_resource.cpp new file mode 100644 index 00000000000..f706a75b950 --- /dev/null +++ b/src/utils/command_manager/auth_resource.cpp @@ -0,0 +1,51 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "utils/command_manager/auth_resource.hpp" +#include "network/crypto.hpp" +#include "utils/string_utils.hpp" +#include "utils/time.hpp" + +void AuthResource::fromXmlNode(const XMLNode* node) +{ + node->get("secret", &m_secret); + node->get("server", &m_server); + node->get("link-format", &m_link_format); +} // AuthResource::AuthResource +//----------------------------------------------------------------------------- + +std::string AuthResource::get(const std::string& username, int online_id) +{ +#ifdef ENABLE_CRYPTO_OPENSSL + std::string header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; + uint64_t timestamp = StkTime::getTimeSinceEpoch(); + std::string payload = "{\"sub\":\"" + username + "/" + std::to_string(online_id) + "\","; + payload += "\"iat\":\"" + std::to_string(timestamp) + "\","; + payload += "\"iss\":\"" + m_server + "\"}"; + header = Crypto::base64url(StringUtils::toUInt8Vector(header)); + payload = Crypto::base64url(StringUtils::toUInt8Vector(payload)); + std::string message = header + "." + payload; + std::string signature = Crypto::base64url(Crypto::hmac_sha256_array(m_secret, message)); + std::string token = message + "." + signature; + std::string response = StringUtils::insertValues(m_link_format, token.c_str()); + return response; +#else + return "This command is currently only supported for OpenSSL"; +#endif +} // AuthResource::get +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/auth_resource.hpp b/src/utils/command_manager/auth_resource.hpp new file mode 100644 index 00000000000..3f8d40f0ada --- /dev/null +++ b/src/utils/command_manager/auth_resource.hpp @@ -0,0 +1,38 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef AUTH_RESOURCE_HPP +#define AUTH_RESOURCE_HPP + +#include +#include "utils/command_manager/abstract_resource.hpp" + +class AuthResource: public AbstractResource +{ +private: + std::string m_secret; + std::string m_server; + std::string m_link_format; + +public: + void fromXmlNode(const XMLNode* node) final; + + std::string get(const std::string& username, int online_id); +}; + +#endif // AUTH_RESOURCE_HPP \ No newline at end of file diff --git a/src/utils/command_manager/command.cpp b/src/utils/command_manager/command.cpp new file mode 100644 index 00000000000..141a89aa829 --- /dev/null +++ b/src/utils/command_manager/command.cpp @@ -0,0 +1,49 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "utils/command_manager/command.hpp" + + + + +Command::Command(std::string name, + std::function f, + int permissions, + int mode_scope, + int state_scope) + : m_name(std::move(name)) + , m_action(std::move(f)) + , m_permissions(permissions) + , m_mode_scope(mode_scope) + , m_state_scope(state_scope) + , m_omit_name(false) +{ + // Handling players who are allowed to run for anyone in any case + m_permissions |= UU_OTHERS_COMMANDS; +} // Command::Command(5) +// ======================================================================== + +void Command::changePermissions(int permissions, + int mode_scope, int state_scope) +{ + // Handling players who are allowed to run for anyone in any case + m_permissions = permissions | UU_OTHERS_COMMANDS; + m_mode_scope = mode_scope; + m_state_scope = state_scope; +} // changePermissions +// ======================================================================== \ No newline at end of file diff --git a/src/utils/command_manager/command.hpp b/src/utils/command_manager/command.hpp new file mode 100644 index 00000000000..c9e4fa6fffa --- /dev/null +++ b/src/utils/command_manager/command.hpp @@ -0,0 +1,111 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef COMMAND_HPP +#define COMMAND_HPP + +#include +#include +#include +#include +#include + +#include "utils/set_typo_fixer.hpp" +#include "network/protocols/command_permissions.hpp" + +struct Context; + +enum ModeScope: int +{ + MS_DEFAULT = 1, + MS_SOCCER_TOURNAMENT = 2 + // add more powers of two if needed +}; + +enum StateScope: int +{ + SS_LOBBY = 1, + SS_INGAME = 2, + SS_ALWAYS = SS_LOBBY | SS_INGAME +}; + +struct CommandDescription +{ + std::string m_usage; + std::string m_permissions; + std::string m_description; + CommandDescription(std::string usage = "", std::string permissions = "", + std::string description = ""): m_usage(usage), + m_permissions(permissions), m_description(description) {} + + std::string getUsage() const { return "Usage: " + m_usage; } + + std::string getHelp() const + { + return "Usage: " + m_usage + + "\nAvailable to: " + m_permissions + + "\n" + m_description; + } +}; + +struct Command +{ + std::string m_name; + + std::string m_prefix_name; + + std::function m_action; + + int m_permissions; + + int m_mode_scope; + + int m_state_scope; + + bool m_omit_name; + + CommandDescription m_description; + + std::weak_ptr m_parent; + + std::map> m_name_to_subcommand; + + std::vector> m_subcommands; + + SetTypoFixer m_stf_subcommand_names; + + Command() {} + + Command(std::string name, + std::function f, + int permissions = UP_EVERYONE, + int mode_scope = MS_DEFAULT, int state_scope = SS_ALWAYS); + + void changeFunction(std::function f) + { m_action = std::move(f); } + + void changePermissions(int permissions = UP_EVERYONE, + int mode_scope = MS_DEFAULT, + int state_scope = SS_ALWAYS); + + std::string getUsage() const { return m_description.getUsage(); } + std::string getHelp() const { return m_description.getHelp(); } + std::string getFullName() const { return m_prefix_name; } +}; + +#endif // COMMAND_HPP \ No newline at end of file diff --git a/src/utils/command_manager/context.cpp b/src/utils/command_manager/context.cpp new file mode 100644 index 00000000000..91a6b791ac3 --- /dev/null +++ b/src/utils/command_manager/context.cpp @@ -0,0 +1,89 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "utils/command_manager/context.hpp" +#include "utils/communication.hpp" + +std::shared_ptr Context::peer() +{ + if (m_peer.expired()) + throw std::logic_error("Peer is expired"); + + auto peer = m_peer.lock(); + if (!peer) + throw std::logic_error("Peer is invalid"); + + return peer; +} // peer +//----------------------------------------------------------------------------- + +std::shared_ptr Context::peerMaybeNull() +{ + // if (m_peer.expired()) + // throw std::logic_error("Peer is expired"); + + auto peer = m_peer.lock(); + return peer; +} // peerMaybeNull +//----------------------------------------------------------------------------- + +std::shared_ptr Context::actingPeer() +{ + if (m_target_peer.expired()) + throw std::logic_error("Target peer is expired"); + + auto acting_peer = m_target_peer.lock(); + if (!acting_peer) + throw std::logic_error("Target peer is invalid"); + + return acting_peer; +} // actingPeer +//----------------------------------------------------------------------------- + +std::shared_ptr Context::actingPeerMaybeNull() +{ + // if (m_target_peer.expired()) + // throw std::logic_error("Target peer is expired"); + + auto acting_peer = m_target_peer.lock(); + return acting_peer; +} // actingPeerMaybeNull +//----------------------------------------------------------------------------- + +std::shared_ptr Context::command() +{ + if (m_command.expired()) + throw std::logic_error("Command is expired"); + + auto command = m_command.lock(); + if (!command) + throw std::logic_error("Command is invalid"); + + return command; +} // command +//----------------------------------------------------------------------------- + +void Context::say(const std::string& s) +{ + if (m_peer.expired()) + throw std::logic_error("Context::say: Peer has expired"); + + auto peer = m_peer.lock(); + Comm::sendStringToPeer(peer, s); +} // say +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/context.hpp b/src/utils/command_manager/context.hpp new file mode 100644 index 00000000000..d17a77625af --- /dev/null +++ b/src/utils/command_manager/context.hpp @@ -0,0 +1,74 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef CONTEXT_HPP +#define CONTEXT_HPP + +#include +#include +#include +#include "network/event.hpp" + +struct Command; +class ServerLobby; + +struct Context +{ + ServerLobby* m_lobby; + + Event* m_event; + + std::weak_ptr m_peer; + + std::weak_ptr m_target_peer; + + std::vector m_argv; + + std::string m_cmd; + + std::weak_ptr m_command; + + int m_user_permissions; + + int m_acting_user_permissions; + + bool m_voting; + + Context(ServerLobby* lobby, Event* event, std::shared_ptr peer): + m_lobby(lobby), + m_event(event), m_peer(peer), m_target_peer(peer), m_argv(), + m_cmd(""), m_user_permissions(0), m_acting_user_permissions(0), m_voting(false) {} + + Context(ServerLobby* lobby, Event* event, std::shared_ptr peer, + std::vector& argv, std::string& cmd, + int user_permissions, int acting_user_permissions, bool voting): + m_lobby(lobby), + m_event(event), m_peer(peer), m_target_peer(peer), m_argv(argv), + m_cmd(cmd), m_user_permissions(user_permissions), + m_acting_user_permissions(acting_user_permissions), m_voting(voting) {} + + std::shared_ptr peer(); + std::shared_ptr peerMaybeNull(); + std::shared_ptr actingPeer(); + std::shared_ptr actingPeerMaybeNull(); + std::shared_ptr command(); + + void say(const std::string& s); +}; + +#endif // CONTEXT_HPP \ No newline at end of file diff --git a/src/utils/command_manager/file_resource.cpp b/src/utils/command_manager/file_resource.cpp new file mode 100644 index 00000000000..cd99720e6e0 --- /dev/null +++ b/src/utils/command_manager/file_resource.cpp @@ -0,0 +1,78 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "utils/command_manager/file_resource.hpp" + +#include +#include "utils/string_utils.hpp" +#include "utils/file_utils.hpp" +#include "utils/time.hpp" + + +void FileResource::fromXmlNode(const XMLNode* node) +{ + node->get("file", &m_file_name); + node->get("interval", &m_interval); + + m_contents = ""; + m_last_invoked = 0; + read(); +} // FileResource +//----------------------------------------------------------------------------- + +void FileResource::read() +{ + if (m_file_name.empty()) // in case it is not properly initialized + return; + + // idk what to do with absolute or relative paths + const std::string& path = /*ServerConfig::getConfigDirectory() + "/" + */m_file_name; + std::ifstream message(FileUtils::getPortableReadingPath(path)); + std::string answer; + + if (message.is_open()) + { + for (std::string line; std::getline(message, line); ) + { + answer += line; + answer.push_back('\n'); + } + if (!answer.empty()) + answer.pop_back(); + } + + m_contents = answer; + m_last_invoked = StkTime::getMonoTimeMs(); +} // read +//----------------------------------------------------------------------------- + +void FileResource::tryUpdate() +{ + uint64_t current_time = StkTime::getMonoTimeMs(); + if (m_interval != 0 && current_time >= m_interval + m_last_invoked) + read(); +} // tryUpdate +//----------------------------------------------------------------------------- + +std::string FileResource::get() +{ + tryUpdate(); + + return m_contents; +} // get +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/file_resource.hpp b/src/utils/command_manager/file_resource.hpp new file mode 100644 index 00000000000..76ee4664c9e --- /dev/null +++ b/src/utils/command_manager/file_resource.hpp @@ -0,0 +1,47 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef FILE_RESOURCE_HPP +#define FILE_RESOURCE_HPP + +#include "utils/types.hpp" +#include "utils/command_manager/abstract_resource.hpp" +#include + +struct FileResource: public AbstractResource +{ +private: + std::string m_file_name; + uint64_t m_interval; + mutable uint64_t m_last_invoked; + std::string m_contents; + + void tryUpdate(); + +protected: + virtual void read(); + +public: + void fromXmlNode(const XMLNode* node) final; + + std::string get(); +}; + + +#endif // FILE_RESOURCE_HPP \ No newline at end of file diff --git a/src/utils/command_manager/text_resource.cpp b/src/utils/command_manager/text_resource.cpp new file mode 100644 index 00000000000..96cadbdf2a5 --- /dev/null +++ b/src/utils/command_manager/text_resource.cpp @@ -0,0 +1,33 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "utils/command_manager/text_resource.hpp" + + + +void TextResource::fromXmlNode(const XMLNode* node) +{ + node->get("text", &m_text); +} // FileResource +//----------------------------------------------------------------------------- + +std::string TextResource::get() +{ + return m_text; +} // get +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/text_resource.hpp b/src/utils/command_manager/text_resource.hpp new file mode 100644 index 00000000000..3320124df4b --- /dev/null +++ b/src/utils/command_manager/text_resource.hpp @@ -0,0 +1,47 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef TEXT_RESOURCE_HPP +#define TEXT_RESOURCE_HPP + +#include "utils/types.hpp" +#include "utils/command_manager/abstract_resource.hpp" +#include + +class TextResource: public AbstractResource +{ +private: + std::string m_text; + +public: + TextResource() {} + + template + TextResource(const T& str): m_text(str) {} + + template + TextResource(T&& str): m_text(std::move(str)) {} + + void fromXmlNode(const XMLNode* node) final; + + std::string get(); +}; + + +#endif // TEXT_RESOURCE_HPP \ No newline at end of file From f7d6224d7f7fde883cd7b147bcbf358d295df509 Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Sat, 23 Aug 2025 13:27:59 +0400 Subject: [PATCH 2/9] Fix compilation, don't pass CommandMgr to Command --- src/network/protocols/command_manager.cpp | 29 +++++++++++------------ src/utils/command_manager/command.cpp | 3 --- src/utils/command_manager/command.hpp | 10 -------- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/network/protocols/command_manager.cpp b/src/network/protocols/command_manager.cpp index 880dbdc183e..1d88c15053a 100644 --- a/src/network/protocols/command_manager.cpp +++ b/src/network/protocols/command_manager.cpp @@ -61,8 +61,6 @@ // TODO: kimden: should decorators use acting_peer? -using Fn = std::function; - namespace { static const std::string g_addon_prefix = "addon_"; @@ -255,11 +253,9 @@ void CommandManager::initCommandsInfo() std::string node_name = node->getName(); if (node_name == "external-commands-file") continue; + // here the commands go std::string name = ""; - // std::string text = ""; // for text-command - // std::string file = ""; // for file-command - // uint64_t interval = 0; // for file-command std::string usage = ""; std::string permissions_s = "UP_EVERYONE"; std::string mode_scope_s = "MS_DEFAULT"; @@ -271,9 +267,6 @@ void CommandManager::initCommandsInfo() std::string permissions_str = ""; std::string description = ""; std::string aliases = ""; - // std::string secret = ""; // for auth-command - // std::string link_format = ""; // for auth-command - // std::string server = ""; // for auth-command // Name is read before enabled/disabled property, because we want // to disable commands in "default" config that are present in @@ -384,12 +377,15 @@ void CommandManager::initCommandsInfo() void CommandManager::initCommands() { + using CM = CommandManager; auto& mp = m_full_name_to_command; - m_root_command = std::make_shared("", &CM::special); + m_root_command = std::make_shared("", std::bind(&CM::special, this, std::placeholders::_1)); initCommandsInfo(); - auto applyFunctionIfPossible = [&](std::string&& name, Fn f) { + auto ptr = this; + + auto applyFunctionIfPossible = [ptr, &mp](std::string&& name, std::function f) { auto it = mp.find(name); if (it == mp.end()) return; @@ -397,8 +393,8 @@ void CommandManager::initCommands() std::shared_ptr command = it->second.lock(); if (!command) return; - - command->changeFunction(std::move(f)); + + command->changeFunction(std::bind(std::move(f), ptr, std::placeholders::_1)); }; // special permissions according to ServerConfig options std::shared_ptr kick_command = mp["kick"].lock(); @@ -944,7 +940,7 @@ void CommandManager::execute(std::shared_ptr command, Context& context) context.m_command = command; try { - (*(command->m_action))(context); + (command->m_action)(context); } catch (std::exception& ex) { @@ -4028,14 +4024,17 @@ std::shared_ptr CommandManager::addChildCommand(std::shared_ptr child = std::make_shared(name, f, - permissions, mode_scope, state_scope); + std::shared_ptr child = std::make_shared(name, + std::bind(f, this, std::placeholders::_1), permissions, mode_scope, state_scope); + target->m_subcommands.push_back(child); child->m_parent = std::weak_ptr(target); + if (target->m_prefix_name.empty()) child->m_prefix_name = name; else child->m_prefix_name = target->m_prefix_name + " " + name; + target->m_name_to_subcommand[name] = std::weak_ptr(child); return child; } // addChildCommand diff --git a/src/utils/command_manager/command.cpp b/src/utils/command_manager/command.cpp index 141a89aa829..86b2542cac4 100644 --- a/src/utils/command_manager/command.cpp +++ b/src/utils/command_manager/command.cpp @@ -18,9 +18,6 @@ #include "utils/command_manager/command.hpp" - - - Command::Command(std::string name, std::function f, int permissions, diff --git a/src/utils/command_manager/command.hpp b/src/utils/command_manager/command.hpp index c9e4fa6fffa..5f58d2d0a93 100644 --- a/src/utils/command_manager/command.hpp +++ b/src/utils/command_manager/command.hpp @@ -66,27 +66,17 @@ struct CommandDescription struct Command { std::string m_name; - std::string m_prefix_name; - std::function m_action; - int m_permissions; - int m_mode_scope; - int m_state_scope; - bool m_omit_name; CommandDescription m_description; - std::weak_ptr m_parent; - std::map> m_name_to_subcommand; - std::vector> m_subcommands; - SetTypoFixer m_stf_subcommand_names; Command() {} From 44f2a291918d93e422fb810aa29846d6e2e89fb7 Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Sun, 24 Aug 2025 00:53:30 +0400 Subject: [PATCH 3/9] Separate command reading from xml from CM --- data/commands.xml | 8 +- src/network/protocols/command_manager.cpp | 361 ++++++-------------- src/network/protocols/command_manager.hpp | 37 +- src/utils/command_manager/auth_resource.cpp | 26 +- src/utils/command_manager/auth_resource.hpp | 10 +- src/utils/command_manager/command.cpp | 258 +++++++++++++- src/utils/command_manager/command.hpp | 73 ++-- src/utils/command_manager/file_resource.cpp | 7 +- src/utils/command_manager/file_resource.hpp | 10 +- src/utils/command_manager/text_resource.cpp | 9 +- src/utils/command_manager/text_resource.hpp | 12 +- 11 files changed, 445 insertions(+), 366 deletions(-) diff --git a/data/commands.xml b/data/commands.xml index f9e25b61683..6c64e3bee34 100644 --- a/data/commands.xml +++ b/data/commands.xml @@ -148,14 +148,14 @@ here later. For now, please refer to the code for the strings being used. --> permissions-verbose="everyone" description="Lists players whom you blocked using /mute command." /> - - state-scope="SS_LOBBY" /> - - getNumNodes(); i++) { const XMLNode *node = current->getNode(i); - std::string node_name = node->getName(); - if (node_name == "external-commands-file") + + auto c = Command::unknownTypeFromXmlNode(node); + if (!c) continue; - // here the commands go - std::string name = ""; - std::string usage = ""; - std::string permissions_s = "UP_EVERYONE"; - std::string mode_scope_s = "MS_DEFAULT"; - std::string state_scope_s = "SS_ALWAYS"; - bool omit_name = false; - int permissions; - int mode_scope; - int state_scope; - std::string permissions_str = ""; - std::string description = ""; - std::string aliases = ""; - - // Name is read before enabled/disabled property, because we want - // to disable commands in "default" config that are present in - // "custom" config, regardless of server name - node->get("name", &name); - if (current == root2 && command == m_root_command - && used_commands.find(name) != used_commands.end()) - { + const std::string name = c->getShortName(); + + if (current == root2 && command == m_root_command && used_commands.find(name) != used_commands.end()) continue; - } else if (current == root) - { used_commands.insert(name); - } - // If enabled is not empty, command is added iff the server name is in enabled - // Otherwise it is added iff the server name is not in disabled - std::string enabled = ""; - std::string disabled = ""; - node->get("enabled", &enabled); - node->get("disabled", &disabled); - std::vector enabled_split = StringUtils::split(enabled, ' '); - std::vector disabled_split = StringUtils::split(disabled, ' '); - bool ok; - if (!enabled.empty()) - { - ok = false; - for (const std::string& s: enabled_split) - if (s == ServerConfig::m_server_uid) - ok = true; - } - else - { - ok = true; - for (const std::string& s: disabled_split) - if (s == ServerConfig::m_server_uid) - ok = false; - } - if (!ok) - continue; - - node->get("usage", &usage); - node->get("permissions", &permissions_s); - permissions = CommandManager::permission_reader.parse(permissions_s); - node->get("mode-scope", &mode_scope_s); - mode_scope = CommandManager::mode_scope_reader.parse(mode_scope_s); - node->get("state-scope", &state_scope_s); - state_scope = CommandManager::state_scope_reader.parse(state_scope_s); - node->get("permissions-verbose", &permissions_str); - node->get("description", &description); - node->get("omit-name", &omit_name); - node->get("aliases", &aliases); - std::vector aliases_split = StringUtils::split(aliases, ' '); - - std::shared_ptr c; - if (node_name == "command") - { - c = addChildCommand(command, name, &CommandManager::special, permissions, mode_scope, state_scope); - } - else if (node_name == "text-command") - { - c = addChildCommand(command, name, &CommandManager::process_text, permissions, mode_scope, state_scope); - TextResource resource; - resource.fromXmlNode(node); - addTextResponse(c->getFullName(), resource); - } - else if (node_name == "file-command") - { - c = addChildCommand(command, name, &CommandManager::process_file, permissions, mode_scope, state_scope); - FileResource resource; - resource.fromXmlNode(node); - addFileResource(c->getFullName(), resource); - } - // else if (node_name == "map-file-command") - // { - // c = addChildCommand(command, name, &CommandManager::process_file, permissions, mode_scope, state_scope); - // MapFileResource resource; - // resource.fromXmlNode(node); - // addMapFileResource(c->getFullName(), resource); - // } - else if (node_name == "auth-command") - { - c = addChildCommand(command, name, &CommandManager::process_auth, permissions, mode_scope, state_scope); - AuthResource resource; - resource.fromXmlNode(node); - addAuthResource(name, resource); - } - c->m_description = CommandDescription(usage, permissions_str, description); + command->addChild(c); m_all_commands.emplace_back(c); m_full_name_to_command[c->getFullName()] = std::weak_ptr(c); - c->m_omit_name = omit_name; - command->m_stf_subcommand_names.add(name); - for (const std::string& alias_name: aliases_split) - { - command->m_stf_subcommand_names.add(alias_name, name); - command->m_name_to_subcommand[alias_name] = command->m_name_to_subcommand[name]; - } dfs(node, c); } }; @@ -379,7 +237,9 @@ void CommandManager::initCommands() { using CM = CommandManager; auto& mp = m_full_name_to_command; - m_root_command = std::make_shared("", std::bind(&CM::special, this, std::placeholders::_1)); + m_root_command = std::make_shared(); + m_root_command->setFunction(getDefaultAction()); + // std::bind(&CM::special, this, std::placeholders::_1)); initCommandsInfo(); @@ -394,15 +254,44 @@ void CommandManager::initCommands() if (!command) return; - command->changeFunction(std::bind(std::move(f), ptr, std::placeholders::_1)); + command->setFunction(std::bind(std::move(f), ptr, std::placeholders::_1)); }; + + auto applyTextIfPossible = [ptr, &mp](std::string&& name, const std::string& value) { + auto it = mp.find(name); + if (it == mp.end()) + return; + + std::shared_ptr command = it->second.lock(); + if (!command) + return; + + std::shared_ptr resource = std::dynamic_pointer_cast(command); + if (!resource) + return; + + resource->setText(value); + }; + // special permissions according to ServerConfig options std::shared_ptr kick_command = mp["kick"].lock(); if (kick_command) { if (getSettings()->hasKicksAllowed()) - kick_command->m_permissions |= UU_CROWNED; + { + kick_command->changePermissions( + kick_command->getPermissions() | UU_CROWNED, + kick_command->getModeScope(), + kick_command->getStateScope() + ); + } else - kick_command->m_permissions &= ~UU_CROWNED; + { + kick_command->changePermissions( + kick_command->getPermissions() & (~UU_CROWNED), + kick_command->getModeScope(), + kick_command->getStateScope() + ); + } } applyFunctionIfPossible("commands", &CM::process_commands); @@ -428,8 +317,6 @@ void CommandManager::initCommands() applyFunctionIfPossible("mute", &CM::process_mute); applyFunctionIfPossible("unmute", &CM::process_unmute); applyFunctionIfPossible("listmute", &CM::process_listmute); - applyFunctionIfPossible("description", &CM::process_text); - applyFunctionIfPossible("moreinfo", &CM::process_text); applyFunctionIfPossible("gnu", &CM::process_gnu); applyFunctionIfPossible("nognu", &CM::process_gnu); applyFunctionIfPossible("tell", &CM::process_tell); @@ -488,8 +375,6 @@ void CommandManager::initCommands() applyFunctionIfPossible("teamhit =", &CM::process_teamhit_assign); applyFunctionIfPossible("scoring", &CM::process_scoring); applyFunctionIfPossible("scoring =", &CM::process_scoring_assign); - applyFunctionIfPossible("version", &CM::process_text); - applyFunctionIfPossible("clear", &CM::process_text); applyFunctionIfPossible("register", &CM::process_register); applyFunctionIfPossible("muteall", &CM::process_muteall); applyFunctionIfPossible("game", &CM::process_game); @@ -541,16 +426,24 @@ void CommandManager::initCommands() applyFunctionIfPossible("liststkaddon", &CM::special); applyFunctionIfPossible("listlocaladdon", &CM::special); - addTextResponse("description", { getSettings()->getMotd() }); - addTextResponse("moreinfo", { getSettings()->getHelpMessage() }); - std::string version = Version::version(); std::string branch = Version::branch(); if (!branch.empty()) version += ", branch " + branch; - addTextResponse("version", { version }); - addTextResponse("clear", std::string(30, '\n')); + applyTextIfPossible("description", getSettings()->getMotd()); + applyTextIfPossible("moreinfo", getSettings()->getHelpMessage()); + applyTextIfPossible("version", version); + applyTextIfPossible("clear", std::string(30, '\n')); + + for (auto& element: m_full_name_to_command) + { + std::shared_ptr command = element.second.lock(); + if (!command || !command->needsFunction() || command->hasFunction()) + continue; + + command->setFunction(getDefaultAction()); + }; // m_votables.emplace("replay", 1.0); m_votables.emplace("start", 0.81); @@ -732,20 +625,20 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) std::shared_ptr executed_command; for (int idx = 0; ; idx++) { - bool one_omittable_subcommand = (current_command->m_subcommands.size() == 1 - && current_command->m_subcommands[0]->m_omit_name); + const auto& subcommands = current_command->getSubcommands(); + bool one_omittable_subcommand = (subcommands.size() == 1 && subcommands[0]->omittedName()); std::shared_ptr command; if (one_omittable_subcommand) { - command = current_command->m_subcommands[0]; + command = subcommands[0]; } else { - if (hasTypo(target_peer_strong, peer, voting, argv, cmd, idx, current_command->m_stf_subcommand_names, 3, false, false)) + if (hasTypo(target_peer_strong, peer, voting, argv, cmd, idx, current_command->subcommandNamesTypoFixer(), 3, false, false)) return; - auto command_iterator = current_command->m_name_to_subcommand.find(argv[idx]); - command = command_iterator->second.lock(); + + command = current_command->findChild(argv[idx]); } if (!command) @@ -762,7 +655,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) // Note that we use caller's permissions to determine if the command can be invoked, // and during its invocation, we use acting peer's permissions. - int mask = ((permissions & command->m_permissions) & (~MASK_MANIPULATION)); + int mask = ((permissions & command->getPermissions()) & (~MASK_MANIPULATION)); if (mask == 0) { context.say("You don't have permissions to " + action + " this command"); @@ -771,7 +664,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) // kimden: both might be nullptr, which means different things if (target_peer.lock() != peer) { - int mask_manip = (permissions & command->m_permissions & MASK_MANIPULATION); + int mask_manip = (permissions & command->getPermissions() & MASK_MANIPULATION); if (mask_manip == 0) { context.say("You don't have permissions to " + action @@ -786,7 +679,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) if (one_omittable_subcommand) --idx; current_command = command; - if (idx + 1 == (int)argv.size() || command->m_subcommands.empty()) { + if (idx + 1 == (int)argv.size() || command->getSubcommands().empty()) { executed_command = command; execute(command, context); break; @@ -850,8 +743,8 @@ int CommandManager::getCurrentModeScope() bool CommandManager::isAvailable(std::shared_ptr c) { - return (getCurrentModeScope() & c->m_mode_scope) != 0 - && (getLobby()->getCurrentStateScope() & c->m_state_scope) != 0; + return (getCurrentModeScope() & c->getModeScope()) != 0 + && (getLobby()->getCurrentStateScope() & c->getStateScope()) != 0; } // getCurrentModeScope // ======================================================================== @@ -864,11 +757,11 @@ void CommandManager::vote(Context& context, std::string category, std::string va if (!acting_peer->hasPlayerProfiles()) return; std::string username = acting_peer->getMainName(); - auto& votable = m_votables[command->m_prefix_name]; + auto& votable = m_votables[command->getFullName()]; bool neededCheck = votable.needsCheck(); votable.castVote(username, category, value); if (votable.needsCheck() && !neededCheck) - m_triggered_votables.push(command->m_prefix_name); + m_triggered_votables.push(command->getFullName()); } // vote // ======================================================================== @@ -940,7 +833,7 @@ void CommandManager::execute(std::shared_ptr command, Context& context) context.m_command = command; try { - (command->m_action)(context); + command->execute(context); } catch (std::exception& ex) { @@ -964,15 +857,18 @@ void CommandManager::process_help(Context& context) auto peer = context.peer(); std::shared_ptr command = m_root_command; - for (int i = 1; i < (int)argv.size(); ++i) { - if (hasTypo(acting_peer, peer, context.m_voting, context.m_argv, context.m_cmd, i, command->m_stf_subcommand_names, 3, false, false)) + for (int i = 1; i < (int)argv.size(); ++i) + { + if (hasTypo(acting_peer, peer, context.m_voting, context.m_argv, context.m_cmd, i, command->subcommandNamesTypoFixer(), 3, false, false)) return; - auto ptr = command->m_name_to_subcommand[argv[i]].lock(); + + auto ptr = command->findChild(argv[i]); if (ptr) command = ptr; else break; - if (command->m_subcommands.empty()) + + if (command->getSubcommands().empty()) break; } if (command == m_root_command) @@ -984,63 +880,6 @@ void CommandManager::process_help(Context& context) } // process_help // ======================================================================== -void CommandManager::process_text(Context& context) -{ - std::string response; - auto command = context.command(); - auto it = m_text_response.find(command->getFullName()); - if (it == m_text_response.end()) - response = StringUtils::insertValues( - "Error: a text command %s is defined without text", - command->getFullName().c_str()); - else - response = it->second.get(); - context.say(response); -} // process_text -// ======================================================================== - -void CommandManager::process_file(Context& context) -{ - std::string response; - auto command = context.command(); - - auto it = m_file_resources.find(command->getFullName()); - if (it == m_file_resources.end()) - response = StringUtils::insertValues( - "Error: file not found for a file command %s", - command->getFullName().c_str()); - else - response = it->second.get(); - context.say(response); -} // process_file -// ======================================================================== - -void CommandManager::process_auth(Context& context) -{ - std::string response; - auto acting_peer = context.actingPeer(); - auto command = context.command(); - - auto it = m_auth_resources.find(command->getFullName()); - if (it == m_auth_resources.end()) - response = StringUtils::insertValues( - "Error: auth method not found for a command %s", - command->getFullName().c_str()); - else - { - auto profile = acting_peer->getMainProfile(); - std::string username = StringUtils::wideToUtf8(profile->getName()); - int online_id = profile->getOnlineId(); - if (online_id == 0) - response = "Error: you need to join with an " - "online account to use auth methods"; - else - response = it->second.get(username, online_id); - } - context.say(response); -} // process_text -// ======================================================================== - void CommandManager::process_commands(Context& context) { std::string result; @@ -1050,13 +889,16 @@ void CommandManager::process_commands(Context& context) auto& argv = context.m_argv; std::shared_ptr command = m_root_command; bool valid_prefix = true; - for (int i = 1; i < (int)argv.size(); ++i) { - if (hasTypo(acting_peer, peer, context.m_voting, context.m_argv, context.m_cmd, i, command->m_stf_subcommand_names, 3, false, false)) + for (int i = 1; i < (int)argv.size(); ++i) + { + if (hasTypo(acting_peer, peer, context.m_voting, context.m_argv, context.m_cmd, i, command->subcommandNamesTypoFixer(), 3, false, false)) return; - auto ptr = command->m_name_to_subcommand[argv[i]].lock(); + + auto ptr = command->findChild(argv[i]); if (!ptr) break; - if ((context.m_acting_user_permissions & ptr->m_permissions) != 0 + + if ((context.m_acting_user_permissions & ptr->getPermissions()) != 0 && isAvailable(ptr)) command = ptr; else @@ -1064,7 +906,7 @@ void CommandManager::process_commands(Context& context) valid_prefix = false; break; } - if (command->m_subcommands.empty()) + if (command->getSubcommands().empty()) break; } if (!valid_prefix) @@ -1077,19 +919,19 @@ void CommandManager::process_commands(Context& context) result += ":"; bool had_any_subcommands = false; std::map res; - for (std::shared_ptr& subcommand: command->m_subcommands) + for (const std::shared_ptr& subcommand: command->getSubcommands()) { - if ((context.m_acting_user_permissions & subcommand->m_permissions) != 0 + if ((context.m_acting_user_permissions & subcommand->getPermissions()) != 0 && isAvailable(subcommand)) { bool subcommands_available = false; - for (auto& c: subcommand->m_subcommands) + for (auto& c: subcommand->getSubcommands()) { - if ((context.m_acting_user_permissions & c->m_permissions) != 0 + if ((context.m_acting_user_permissions & c->getPermissions()) != 0 && isAvailable(c)) subcommands_available = true; } - res[subcommand->m_name] = subcommands_available; + res[subcommand->getShortName()] = subcommands_available; if (subcommands_available) had_any_subcommands = true; } @@ -4020,25 +3862,6 @@ void CommandManager::onStartSelection() update(); } // onStartSelection // ======================================================================== -std::shared_ptr CommandManager::addChildCommand(std::shared_ptr target, - std::string name, void (CommandManager::*f)(Context& context), - int permissions, int mode_scope, int state_scope) -{ - std::shared_ptr child = std::make_shared(name, - std::bind(f, this, std::placeholders::_1), permissions, mode_scope, state_scope); - - target->m_subcommands.push_back(child); - child->m_parent = std::weak_ptr(target); - - if (target->m_prefix_name.empty()) - child->m_prefix_name = name; - else - child->m_prefix_name = target->m_prefix_name + " " + name; - - target->m_name_to_subcommand[name] = std::weak_ptr(child); - return child; -} // addChildCommand -// ======================================================================== std::string CommandManager::getAddonPreferredType() const { @@ -4111,4 +3934,10 @@ void CommandManager::shift(std::string& cmd, std::vector& argv, CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); } // shift +//----------------------------------------------------------------------------- + +std::function CommandManager::getDefaultAction() +{ + return std::bind(&CommandManager::special, this, std::placeholders::_1); +} // getDefaultAction //----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/network/protocols/command_manager.hpp b/src/network/protocols/command_manager.hpp index 89c3b7800c7..5ae687fba22 100644 --- a/src/network/protocols/command_manager.hpp +++ b/src/network/protocols/command_manager.hpp @@ -45,7 +45,6 @@ #include "utils/command_manager/context.hpp" #include "utils/command_manager/file_resource.hpp" #include "utils/command_manager/text_resource.hpp" -#include "utils/enum_extended_reader.hpp" #include "utils/lobby_context.hpp" #include "utils/set_typo_fixer.hpp" #include "utils/team_utils.hpp" @@ -61,11 +60,6 @@ class STKPeer; class CommandManager: public LobbyContextComponent { private: - - static EnumExtendedReader permission_reader; - static EnumExtendedReader mode_scope_reader; - static EnumExtendedReader state_scope_reader; - std::deque>& get_queue(int x) const; template @@ -81,12 +75,6 @@ class CommandManager: public LobbyContextComponent std::multiset m_users; - std::map m_text_response; - - std::map m_file_resources; - - std::map m_auth_resources; - std::map m_votables; std::queue m_triggered_votables; @@ -136,9 +124,6 @@ class CommandManager: public LobbyContextComponent void execute(std::shared_ptr command, Context& context); void process_help(Context& context); - void process_text(Context& context); - void process_file(Context& context); - void process_auth(Context& context); void process_commands(Context& context); void process_replay(Context& context); void process_start(Context& context); @@ -240,22 +225,6 @@ class CommandManager: public LobbyContextComponent void handleCommand(Event* event, std::shared_ptr peer); - // template - // void addTextResponse(std::string key, T&& value) - // { m_text_response[key] = value; } - - void addTextResponse(std::string key, const TextResource& resource) - { m_text_response[key] = resource; } - - void addTextResponse(std::string key, TextResource&& resource) - { m_text_response[key] = std::move(resource); } - - void addFileResource(std::string key, const FileResource& resource) - { m_file_resources[key] = resource; } - - void addAuthResource(std::string key, const AuthResource& resource) - { m_auth_resources[key] = resource; } - void addUser(std::string& s); void deleteUser(std::string& s); @@ -280,16 +249,14 @@ class CommandManager: public LobbyContextComponent std::vector getCurrentArgv() { return m_current_argv; } - std::shared_ptr addChildCommand(std::shared_ptr target, std::string name, - void (CommandManager::*f)(Context& context), int permissions = UP_EVERYONE, - int mode_scope = MS_DEFAULT, int state_scope = SS_ALWAYS); - // Helper functions, unrelated to CommandManager inner structure std::string getAddonPreferredType() const; void shift(std::string& cmd, std::vector& argv, const std::string& username, int count); + std::function getDefaultAction(); + }; #endif // COMMAND_MANAGER_HPP diff --git a/src/utils/command_manager/auth_resource.cpp b/src/utils/command_manager/auth_resource.cpp index f706a75b950..fe151f3ccb6 100644 --- a/src/utils/command_manager/auth_resource.cpp +++ b/src/utils/command_manager/auth_resource.cpp @@ -20,16 +20,19 @@ #include "network/crypto.hpp" #include "utils/string_utils.hpp" #include "utils/time.hpp" +#include "network/stk_peer.hpp" +#include "network/network_player_profile.hpp" void AuthResource::fromXmlNode(const XMLNode* node) -{ +{ + Command::fromXmlNode(node); node->get("secret", &m_secret); node->get("server", &m_server); node->get("link-format", &m_link_format); } // AuthResource::AuthResource //----------------------------------------------------------------------------- -std::string AuthResource::get(const std::string& username, int online_id) +std::string AuthResource::get(const std::string& username, int online_id) const { #ifdef ENABLE_CRYPTO_OPENSSL std::string header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; @@ -48,4 +51,23 @@ std::string AuthResource::get(const std::string& username, int online_id) return "This command is currently only supported for OpenSSL"; #endif } // AuthResource::get +//----------------------------------------------------------------------------- + +void AuthResource::execute(Context& context) +{ + std::string response; + auto acting_peer = context.actingPeer(); + auto command = context.command(); + + auto profile = acting_peer->getMainProfile(); + std::string username = StringUtils::wideToUtf8(profile->getName()); + int online_id = profile->getOnlineId(); + if (online_id == 0) + response = "Error: you need to join with an " + "online account to use auth methods"; + else + response = get(username, online_id); + + context.say(response); +} // execute //----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/auth_resource.hpp b/src/utils/command_manager/auth_resource.hpp index 3f8d40f0ada..1c5c835cb56 100644 --- a/src/utils/command_manager/auth_resource.hpp +++ b/src/utils/command_manager/auth_resource.hpp @@ -20,19 +20,23 @@ #define AUTH_RESOURCE_HPP #include -#include "utils/command_manager/abstract_resource.hpp" +#include "utils/command_manager/command.hpp" -class AuthResource: public AbstractResource +class AuthResource: public Command { private: std::string m_secret; std::string m_server; std::string m_link_format; + std::string get(const std::string& username, int online_id) const; + public: + bool needsFunction() const final { return false; } + void fromXmlNode(const XMLNode* node) final; - std::string get(const std::string& username, int online_id); + void execute(Context& context) final; }; #endif // AUTH_RESOURCE_HPP \ No newline at end of file diff --git a/src/utils/command_manager/command.cpp b/src/utils/command_manager/command.cpp index 86b2542cac4..37c3c948844 100644 --- a/src/utils/command_manager/command.cpp +++ b/src/utils/command_manager/command.cpp @@ -17,23 +17,101 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "utils/command_manager/command.hpp" +#include "utils/string_utils.hpp" +#include "network/server_config.hpp" +#include "utils/enum_extended_reader.hpp" +#include "utils/log.hpp" -Command::Command(std::string name, - std::function f, - int permissions, - int mode_scope, - int state_scope) - : m_name(std::move(name)) - , m_action(std::move(f)) - , m_permissions(permissions) - , m_mode_scope(mode_scope) - , m_state_scope(state_scope) - , m_omit_name(false) +#include "utils/command_manager/auth_resource.hpp" +#include "utils/command_manager/file_resource.hpp" +#include "utils/command_manager/text_resource.hpp" + +// This is needed for default actions only for now... +#include "network/protocols/command_manager.hpp" + +#include + +namespace { - // Handling players who are allowed to run for anyone in any case - m_permissions |= UU_OTHERS_COMMANDS; -} // Command::Command(5) -// ======================================================================== + static EnumExtendedReader mode_scope_reader({ + {"MS_DEFAULT", MS_DEFAULT}, + {"MS_SOCCER_TOURNAMENT", MS_SOCCER_TOURNAMENT} + }); + + static EnumExtendedReader state_scope_reader({ + {"SS_LOBBY", SS_LOBBY}, + {"SS_INGAME", SS_INGAME}, + {"SS_ALWAYS", SS_ALWAYS} + }); + + static EnumExtendedReader permission_reader({ + {"PE_NONE", PE_NONE}, + {"UU_SPECTATOR", UU_SPECTATOR}, + {"UU_USUAL", UU_USUAL}, + {"UU_CROWNED", UU_CROWNED}, + {"UU_SINGLE", UU_SINGLE}, + {"UU_HAMMER", UU_HAMMER}, + {"UU_MANIPULATOR", UU_MANIPULATOR}, + {"UU_CONSOLE", UU_CONSOLE}, + {"PE_SPECTATOR", PE_SPECTATOR}, + {"PE_USUAL", PE_USUAL}, + {"PE_CROWNED", PE_CROWNED}, + {"PE_SINGLE", PE_SINGLE}, + {"PE_HAMMER", PE_HAMMER}, + {"PE_MANIPULATOR", PE_MANIPULATOR}, + {"PE_CONSOLE", PE_CONSOLE}, + {"UU_OWN_COMMANDS", UU_OWN_COMMANDS}, + {"UU_OTHERS_COMMANDS", UU_OTHERS_COMMANDS}, + {"PE_ALLOW_ANYONE", PE_ALLOW_ANYONE}, + {"PE_VOTED_SPECTATOR", PE_VOTED_SPECTATOR}, + {"PE_VOTED_NORMAL", PE_VOTED_NORMAL}, + {"PE_VOTED", PE_VOTED}, + {"UP_CONSOLE", UP_CONSOLE}, + {"UP_MANIPULATOR", UP_MANIPULATOR}, + {"UP_HAMMER", UP_HAMMER}, + {"UP_SINGLE", UP_SINGLE}, + {"UP_CROWNED", UP_CROWNED}, + {"UP_NORMAL", UP_NORMAL}, + {"UP_EVERYONE", UP_EVERYONE} + }); + + // C++20 compile-time string to class conversion + + // template + // struct XmlNameToClass; + + // template<> struct XmlNameToClass<"command"> { using type = Command; }; + // template<> struct XmlNameToClass<"text-command"> { using type = TextResource; }; + // template<> struct XmlNameToClass<"file-command"> { using type = FileResource; }; + // template<> struct XmlNameToClass<"auth-command"> { using type = AuthResource; }; + +} // namespace +//============================================================================= + +CommandDescription::CommandDescription(std::string usage, + std::string permissions, + std::string description) + : m_usage(std::move(usage)) + , m_permissions(std::move(permissions)) + , m_description(std::move(description)) +{} +//----------------------------------------------------------------------------- + +std::string CommandDescription::getUsage() const +{ + return StringUtils::insertValues("Usage: %s", m_usage.c_str()); +} // getUsage +//----------------------------------------------------------------------------- + +std::string CommandDescription::getHelp() const +{ + return StringUtils::insertValues("Usage: %s\nAvailable to: %s\n%s", + m_usage.c_str(), + m_permissions.c_str(), + m_description.c_str() + ); +} // getHelp +//============================================================================= void Command::changePermissions(int permissions, int mode_scope, int state_scope) @@ -42,5 +120,151 @@ void Command::changePermissions(int permissions, m_permissions = permissions | UU_OTHERS_COMMANDS; m_mode_scope = mode_scope; m_state_scope = state_scope; -} // changePermissions -// ======================================================================== \ No newline at end of file +} // changePermissions +//----------------------------------------------------------------------------- + + +std::shared_ptr Command::unknownTypeFromXmlNode(const XMLNode* node) +{ + std::string node_name = node->getName(); + if (node_name == "external-commands-file") + return {}; + + std::shared_ptr res; + if (node_name == "command") + res = std::make_shared(); + else if (node_name == "text-command") + res = std::make_shared(); + else if (node_name == "auth-command") + res = std::make_shared(); + else if (node_name == "file-command") + res = std::make_shared(); + else + { + Log::error("Command", "Unknown node name %s, treating as normal command.", node_name.c_str()); + res = std::make_shared(); + } + + try + { + res->fromXmlNode(node); + } + catch (std::exception& ex) + { + Log::error("Command", "unknownTypeFromXmlNode: error while calling fromXmlNode: %s", ex.what()); + return {}; + } + + return res; +} + +void Command::fromXmlNode(const XMLNode* node) +{ + std::string name = ""; + std::string usage = ""; + std::string permissions_s = "UP_EVERYONE"; + std::string mode_scope_s = "MS_DEFAULT"; + std::string state_scope_s = "SS_ALWAYS"; + bool omit_name = false; + int permissions; + int mode_scope; + int state_scope; + std::string permissions_str = ""; + std::string description = ""; + std::string aliases = ""; + + node->get("name", &name); + m_name = name; + + // If enabled is not empty, command is added iff the server name is in enabled + // Otherwise it is added iff the server name is not in disabled + std::string enabled = ""; + std::string disabled = ""; + node->get("enabled", &enabled); + node->get("disabled", &disabled); + bool ok; + if (!enabled.empty()) + { + std::vector enabled_split = StringUtils::split(enabled, ' '); + ok = false; + for (const std::string& s: enabled_split) + if (s == ServerConfig::m_server_uid) + ok = true; + } + else + { + std::vector disabled_split = StringUtils::split(disabled, ' '); + ok = true; + for (const std::string& s: disabled_split) + if (s == ServerConfig::m_server_uid) + ok = false; + } + + if (!ok) + { + throw std::logic_error(StringUtils::insertValues( + "The command %s is not loaded, as it was disabled in the config.", + name.c_str() + )); + } + + node->get("permissions", &permissions_s); + node->get("mode-scope", &mode_scope_s); + node->get("state-scope", &state_scope_s); + permissions = permission_reader.parse(permissions_s); + mode_scope = mode_scope_reader.parse(mode_scope_s); + state_scope = state_scope_reader.parse(state_scope_s); + changePermissions(permissions, mode_scope, state_scope); + + node->get("usage", &usage); + node->get("permissions-verbose", &permissions_str); + node->get("description", &description); + m_description = CommandDescription(usage, permissions_str, description); + + node->get("aliases", &aliases); + m_aliases = StringUtils::split(aliases, ' '); + + node->get("omit-name", &omit_name); + m_omit_name = omit_name; + + // m_action is set in CommandManager, as it knows better about implementation. + // CM::special is also set from there. +} // fromXmlNode +//----------------------------------------------------------------------------- + +void Command::addChild(const std::shared_ptr& child) +{ + child->m_parent = shared_from_this(); + const std::string name = child->m_name; + + m_subcommands.push_back(child); + child->m_prefix_name = + (m_prefix_name.empty() ? "" : m_prefix_name + " ") + name; + + auto weak = std::weak_ptr(child); + m_name_to_subcommand[name] = weak; + + m_stf_subcommand_names.add(name); + for (const std::string& alias_name: child->getAliases()) + { + m_stf_subcommand_names.add(alias_name, name); + m_name_to_subcommand[alias_name] = m_name_to_subcommand[name]; + } +} // addChild +//----------------------------------------------------------------------------- + +void Command::execute(Context& context) +{ + m_action(context); +} // execute +//----------------------------------------------------------------------------- + +std::shared_ptr Command::findChild(const std::string& name) const +{ + const auto& it = m_name_to_subcommand.find(name); + if (it == m_name_to_subcommand.end()) + return {}; + + return it->second.lock(); +} // findChild +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/command.hpp b/src/utils/command_manager/command.hpp index 5f58d2d0a93..cec4551c896 100644 --- a/src/utils/command_manager/command.hpp +++ b/src/utils/command_manager/command.hpp @@ -25,8 +25,10 @@ #include #include -#include "utils/set_typo_fixer.hpp" #include "network/protocols/command_permissions.hpp" +#include "utils/set_typo_fixer.hpp" +#include "io/xml_node.hpp" +#include "utils/command_manager/context.hpp" struct Context; @@ -49,29 +51,26 @@ struct CommandDescription std::string m_usage; std::string m_permissions; std::string m_description; - CommandDescription(std::string usage = "", std::string permissions = "", - std::string description = ""): m_usage(usage), - m_permissions(permissions), m_description(description) {} - - std::string getUsage() const { return "Usage: " + m_usage; } - - std::string getHelp() const - { - return "Usage: " + m_usage - + "\nAvailable to: " + m_permissions - + "\n" + m_description; - } + CommandDescription(std::string usage = "", + std::string permissions = "", + std::string description = ""); + + std::string getUsage() const; + + std::string getHelp() const; }; -struct Command +struct Command: public std::enable_shared_from_this { +private: std::string m_name; std::string m_prefix_name; - std::function m_action; - int m_permissions; - int m_mode_scope; - int m_state_scope; + std::function m_action = nullptr; + int m_permissions = UP_EVERYONE; + int m_mode_scope = MS_DEFAULT; + int m_state_scope = SS_ALWAYS; bool m_omit_name; + std::vector m_aliases; CommandDescription m_description; std::weak_ptr m_parent; @@ -79,14 +78,22 @@ struct Command std::vector> m_subcommands; SetTypoFixer m_stf_subcommand_names; +protected: + virtual void fromXmlNode(const XMLNode* node); + +public: + virtual bool needsFunction() const { return true; } + Command() {} - Command(std::string name, - std::function f, - int permissions = UP_EVERYONE, - int mode_scope = MS_DEFAULT, int state_scope = SS_ALWAYS); + // Command(std::string name, + // std::function f, + // int permissions = UP_EVERYONE, + // int mode_scope = MS_DEFAULT, int state_scope = SS_ALWAYS); + + static std::shared_ptr unknownTypeFromXmlNode(const XMLNode* node); - void changeFunction(std::function f) + void setFunction(std::function f) { m_action = std::move(f); } void changePermissions(int permissions = UP_EVERYONE, @@ -96,6 +103,26 @@ struct Command std::string getUsage() const { return m_description.getUsage(); } std::string getHelp() const { return m_description.getHelp(); } std::string getFullName() const { return m_prefix_name; } + std::string getShortName() const { return m_name; } + int getPermissions() const { return m_permissions; } + int getModeScope() const { return m_mode_scope; } + int getStateScope() const { return m_state_scope; } + bool omittedName() const { return m_omit_name; } + bool hasFunction() const { return m_action != nullptr; } + + const SetTypoFixer& subcommandNamesTypoFixer() const + { return m_stf_subcommand_names; } + + std::shared_ptr findChild(const std::string& name) const; + + const std::vector>& getSubcommands() const + { return m_subcommands; } + + void addChild(const std::shared_ptr& child); + + const std::vector& getAliases() const { return m_aliases; } + + virtual void execute(Context& context); }; #endif // COMMAND_HPP \ No newline at end of file diff --git a/src/utils/command_manager/file_resource.cpp b/src/utils/command_manager/file_resource.cpp index cd99720e6e0..8e991a387e6 100644 --- a/src/utils/command_manager/file_resource.cpp +++ b/src/utils/command_manager/file_resource.cpp @@ -26,6 +26,7 @@ void FileResource::fromXmlNode(const XMLNode* node) { + Command::fromXmlNode(node); node->get("file", &m_file_name); node->get("interval", &m_interval); @@ -69,10 +70,10 @@ void FileResource::tryUpdate() } // tryUpdate //----------------------------------------------------------------------------- -std::string FileResource::get() +void FileResource::execute(Context& context) { tryUpdate(); - return m_contents; -} // get + context.say(m_contents); +} // execute //----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/file_resource.hpp b/src/utils/command_manager/file_resource.hpp index 76ee4664c9e..6bba151b9a9 100644 --- a/src/utils/command_manager/file_resource.hpp +++ b/src/utils/command_manager/file_resource.hpp @@ -21,10 +21,10 @@ #define FILE_RESOURCE_HPP #include "utils/types.hpp" -#include "utils/command_manager/abstract_resource.hpp" +#include "utils/command_manager/command.hpp" #include -struct FileResource: public AbstractResource +struct FileResource: public Command { private: std::string m_file_name; @@ -38,9 +38,11 @@ struct FileResource: public AbstractResource virtual void read(); public: - void fromXmlNode(const XMLNode* node) final; + bool needsFunction() const final { return false; } - std::string get(); + virtual void fromXmlNode(const XMLNode* node) override; + + virtual void execute(Context& context) override; }; diff --git a/src/utils/command_manager/text_resource.cpp b/src/utils/command_manager/text_resource.cpp index 96cadbdf2a5..80a28e9e510 100644 --- a/src/utils/command_manager/text_resource.cpp +++ b/src/utils/command_manager/text_resource.cpp @@ -18,16 +18,15 @@ #include "utils/command_manager/text_resource.hpp" - - void TextResource::fromXmlNode(const XMLNode* node) { + Command::fromXmlNode(node); node->get("text", &m_text); } // FileResource //----------------------------------------------------------------------------- -std::string TextResource::get() +void TextResource::execute(Context& context) { - return m_text; -} // get + context.say(m_text); +} // execute //----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/text_resource.hpp b/src/utils/command_manager/text_resource.hpp index 3320124df4b..c5695020d41 100644 --- a/src/utils/command_manager/text_resource.hpp +++ b/src/utils/command_manager/text_resource.hpp @@ -21,16 +21,18 @@ #define TEXT_RESOURCE_HPP #include "utils/types.hpp" -#include "utils/command_manager/abstract_resource.hpp" +#include "utils/command_manager/command.hpp" #include -class TextResource: public AbstractResource +class TextResource: public Command { private: std::string m_text; public: - TextResource() {} + bool needsFunction() const final { return false; } + + TextResource(): m_text("") {} template TextResource(const T& str): m_text(str) {} @@ -38,9 +40,11 @@ class TextResource: public AbstractResource template TextResource(T&& str): m_text(std::move(str)) {} + void setText(const std::string& str) { m_text = str; } + void fromXmlNode(const XMLNode* node) final; - std::string get(); + void execute(Context& context) final; }; From 336fcfd00106a2b7ea7046e08320e2b04b6b8c1e Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Sun, 24 Aug 2025 01:02:50 +0400 Subject: [PATCH 4/9] Delete unused AbstractResource which was merged into existing Command class --- .../command_manager/abstract_resource.hpp | 40 ------------------- 1 file changed, 40 deletions(-) delete mode 100644 src/utils/command_manager/abstract_resource.hpp diff --git a/src/utils/command_manager/abstract_resource.hpp b/src/utils/command_manager/abstract_resource.hpp deleted file mode 100644 index e5a80725038..00000000000 --- a/src/utils/command_manager/abstract_resource.hpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2025 kimden -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 3 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -#ifndef ABSTRACT_RESOURCE_HPP -#define ABSTRACT_RESOURCE_HPP - -#include -#include "io/xml_node.hpp" - -/** - * AbstractResource represents a generic externally produced resource - * (that is, typically not produced inside the game, or produced inside the game, - * but further processed elsewhere). - */ -class AbstractResource -{ -public: - - virtual void fromXmlNode(const XMLNode* node) = 0; - - // virtual std::string process(Context& context) = 0; -}; - - -#endif // ABSTRACT_RESOURCE_HPP \ No newline at end of file From ab110fa569a6a9b491c372f48259afd17e07ce99 Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Sun, 24 Aug 2025 02:38:48 +0400 Subject: [PATCH 5/9] Basic MapFileResource implementation + method moves - restoreCmdByArgv and error(context) were moved out of CommandManager - MapFileResource currently supports searching rows using a column specified in the config --- src/network/protocols/command_manager.cpp | 69 +++------ src/network/protocols/command_manager.hpp | 4 - src/utils/command_manager/command.cpp | 6 +- src/utils/command_manager/context.cpp | 32 ++++ src/utils/command_manager/context.hpp | 2 + src/utils/command_manager/file_resource.cpp | 9 +- src/utils/command_manager/file_resource.hpp | 7 +- .../command_manager/map_file_resource.cpp | 142 ++++++++++++++++++ .../command_manager/map_file_resource.hpp | 63 ++++++++ src/utils/set_typo_fixer.cpp | 7 + src/utils/set_typo_fixer.hpp | 1 + src/utils/string_utils.cpp | 16 ++ src/utils/string_utils.hpp | 3 + 13 files changed, 303 insertions(+), 58 deletions(-) create mode 100644 src/utils/command_manager/map_file_resource.cpp create mode 100644 src/utils/command_manager/map_file_resource.hpp diff --git a/src/network/protocols/command_manager.cpp b/src/network/protocols/command_manager.cpp index 54cf9c9e24b..a173cb40881 100644 --- a/src/network/protocols/command_manager.cpp +++ b/src/network/protocols/command_manager.cpp @@ -124,6 +124,15 @@ namespace return QM_NONE; } // another_cyclic_queue // ==================================================================== + + void restoreCmdByArgv(std::string& cmd, + std::vector& argv, char c, char d, char e, char f, + int from = 0) + { + cmd = StringUtils::quoteEscapeArray(argv.begin() + from, argv.end(), + c, d, e, f); + } // restoreCmdByArgv + // ======================================================================== // Auxiliary things, should be moved somewhere because they just help @@ -520,7 +529,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) argv = StringUtils::splitQuoted(cmd, ' ', '"', '"', '\\'); if (argv.empty()) return; - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); permissions = getLobby()->getPermissions(peer); voting = false; @@ -543,7 +552,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) argv = StringUtils::splitQuoted(cmd, ' ', '"', '"', '\\'); if (argv.empty()) return; - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); voting = m_user_saved_voting[username]; target_peer = m_user_saved_acting_peer[username]; target_peer_strong = target_peer.lock(); @@ -717,7 +726,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) { std::string new_cmd = p.first + " " + p.second; auto new_argv = StringUtils::splitQuoted(new_cmd, ' ', '"', '"', '\\'); - CommandManager::restoreCmdByArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); + restoreCmdByArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); std::string msg2 = StringUtils::insertValues( "Command \"/%s\" has been successfully voted", new_cmd.c_str()); @@ -778,7 +787,7 @@ void CommandManager::update() { std::string new_cmd = p.first + " " + p.second; auto new_argv = StringUtils::splitQuoted(new_cmd, ' ', '"', '"', '\\'); - CommandManager::restoreCmdByArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); + restoreCmdByArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); std::string msg = StringUtils::insertValues( "Command \"/%s\" has been successfully voted", new_cmd.c_str()); @@ -802,29 +811,9 @@ void CommandManager::update() void CommandManager::error(Context& context, bool is_error) { - std::string msg; - if (is_error) - Log::error("CommandManager", "An error occurred while invoking %s", context.m_cmd.c_str()); - auto command = context.m_command.lock(); - auto peer = context.m_peer.lock(); - if (!command) { - Log::error("CommandManager", "CM::error: cannot load command"); - return; - } - if (!peer) { - Log::error("CommandManager", "CM::error: cannot load peer to send error"); - return; - } - msg = command->getUsage(); - if (msg.empty()) - msg = StringUtils::insertValues("An error occurred " - "while invoking command \"%s\".", - command->getFullName().c_str()); - - if (is_error) - msg += "\n/!\\ Please report this error to the server owner"; - context.say(msg); -} // error + // When you have no time, change all error(context) calls to context.error() + context.error(is_error); +} // error // ======================================================================== void CommandManager::execute(std::shared_ptr command, Context& context) @@ -2231,7 +2220,7 @@ void CommandManager::process_queue_push(Context& context) argv[2] = asset_manager->getRandomAddonMap(); std::string filter_text = ""; - CommandManager::restoreCmdByArgv(filter_text, argv, ' ', '"', '"', '\\', 2); + restoreCmdByArgv(filter_text, argv, ' ', '"', '"', '\\', 2); // Fix typos only if track queues are used (majority of cases anyway) // TODO: I don't know how to fix typos for both karts and tracks @@ -2864,7 +2853,7 @@ void CommandManager::process_scoring_assign(Context& context) auto& argv = context.m_argv; std::string cmd2; - CommandManager::restoreCmdByArgv(cmd2, argv, ' ', '"', '"', '\\', 1); + restoreCmdByArgv(cmd2, argv, ' ', '"', '"', '\\', 1); if (getGPManager()->trySettingGPScoring(cmd2)) Comm::sendStringToAllPeers("Scoring set to \"" + cmd2 + "\""); else @@ -3743,20 +3732,6 @@ void CommandManager::deleteUser(std::string& s) } // deleteUser // ======================================================================== -void CommandManager::restoreCmdByArgv(std::string& cmd, - std::vector& argv, char c, char d, char e, char f, - int from) -{ - cmd.clear(); - for (int i = from; i < (int)argv.size(); ++i) { - if (i > from) { - cmd.push_back(c); - } - cmd += StringUtils::quoteEscape(argv[i], c, d, e, f); - } -} // restoreCmdByArgv -// ======================================================================== - bool CommandManager::validate(Context& ctx, int idx, TypoFixerType fixer_type, bool case_sensitive, bool allow_as_is) { @@ -3827,12 +3802,12 @@ bool CommandManager::hasTypo(std::shared_ptr acting_peer, std::shared_p } for (unsigned i = 0; i < closest_commands.size(); ++i) { argv[idx] = prefix + closest_commands[i].first + suffix; - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); m_user_command_replacements[username].push_back(cmd); response += "\ntype /" + std::to_string(i + 1) + " to choose \"" + closest_commands[i].first + "\""; } argv[idx] = initial_argument; - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); Comm::sendStringToPeer(peer, response); return true; } @@ -3840,7 +3815,7 @@ bool CommandManager::hasTypo(std::shared_ptr acting_peer, std::shared_p if (!dont_replace) { argv[idx] = prefix + closest_commands[0].first + suffix; // converts case or regex - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); } return false; } // hasTypo @@ -3932,7 +3907,7 @@ void CommandManager::shift(std::string& cmd, std::vector& argv, m_user_last_correct_argument[username].first -= count; - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); } // shift //----------------------------------------------------------------------------- diff --git a/src/network/protocols/command_manager.hpp b/src/network/protocols/command_manager.hpp index 5ae687fba22..2706d4d22f7 100644 --- a/src/network/protocols/command_manager.hpp +++ b/src/network/protocols/command_manager.hpp @@ -229,10 +229,6 @@ class CommandManager: public LobbyContextComponent void deleteUser(std::string& s); - static void restoreCmdByArgv(std::string& cmd, - std::vector& argv, char c, char d, char e, char f, - int from = 0); - // A simple version of hasTypo for validating simple arguments. // Returns the opposite bool value. bool validate(Context& ctx, int idx, diff --git a/src/utils/command_manager/command.cpp b/src/utils/command_manager/command.cpp index 37c3c948844..521dc55c457 100644 --- a/src/utils/command_manager/command.cpp +++ b/src/utils/command_manager/command.cpp @@ -24,11 +24,9 @@ #include "utils/command_manager/auth_resource.hpp" #include "utils/command_manager/file_resource.hpp" +#include "utils/command_manager/map_file_resource.hpp" #include "utils/command_manager/text_resource.hpp" -// This is needed for default actions only for now... -#include "network/protocols/command_manager.hpp" - #include namespace @@ -139,6 +137,8 @@ std::shared_ptr Command::unknownTypeFromXmlNode(const XMLNode* node) res = std::make_shared(); else if (node_name == "file-command") res = std::make_shared(); + else if (node_name == "map-file-command") + res = std::make_shared(); else { Log::error("Command", "Unknown node name %s, treating as normal command.", node_name.c_str()); diff --git a/src/utils/command_manager/context.cpp b/src/utils/command_manager/context.cpp index 91a6b791ac3..c9949ec950a 100644 --- a/src/utils/command_manager/context.cpp +++ b/src/utils/command_manager/context.cpp @@ -18,6 +18,9 @@ #include "utils/command_manager/context.hpp" #include "utils/communication.hpp" +#include "utils/command_manager/command.hpp" +#include "utils/string_utils.hpp" +#include "utils/log.hpp" std::shared_ptr Context::peer() { @@ -86,4 +89,33 @@ void Context::say(const std::string& s) auto peer = m_peer.lock(); Comm::sendStringToPeer(peer, s); } // say +//----------------------------------------------------------------------------- + +void Context::error(bool is_error) +{ + std::string msg; + if (is_error) + Log::error("CMContext", "An error occurred while invoking %s", m_cmd.c_str()); + + auto command = m_command.lock(); + auto peer = m_peer.lock(); + if (!command) { + Log::error("CMContext", "CM::error: cannot load command"); + return; + } + if (!peer) { + Log::error("CMContext", "CM::error: cannot load peer to send error"); + return; + } + msg = command->getUsage(); + if (msg.empty()) + msg = StringUtils::insertValues("An error occurred " + "while invoking command \"%s\".", + command->getFullName().c_str()); + + if (is_error) + msg += "\n/!\\ Please report this error to the server owner"; + + say(msg); +} // error //----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/context.hpp b/src/utils/command_manager/context.hpp index d17a77625af..bad25ca5784 100644 --- a/src/utils/command_manager/context.hpp +++ b/src/utils/command_manager/context.hpp @@ -69,6 +69,8 @@ struct Context std::shared_ptr command(); void say(const std::string& s); + + void error(bool is_error = false); }; #endif // CONTEXT_HPP \ No newline at end of file diff --git a/src/utils/command_manager/file_resource.cpp b/src/utils/command_manager/file_resource.cpp index 8e991a387e6..96adcdbfa17 100644 --- a/src/utils/command_manager/file_resource.cpp +++ b/src/utils/command_manager/file_resource.cpp @@ -32,7 +32,6 @@ void FileResource::fromXmlNode(const XMLNode* node) m_contents = ""; m_last_invoked = 0; - read(); } // FileResource //----------------------------------------------------------------------------- @@ -57,8 +56,14 @@ void FileResource::read() answer.pop_back(); } - m_contents = answer; m_last_invoked = StkTime::getMonoTimeMs(); + + // Don't do anything if the file is the same + if (m_contents != answer) + { + m_contents = answer; + onContentChange(); + } } // read //----------------------------------------------------------------------------- diff --git a/src/utils/command_manager/file_resource.hpp b/src/utils/command_manager/file_resource.hpp index 6bba151b9a9..c9badea9716 100644 --- a/src/utils/command_manager/file_resource.hpp +++ b/src/utils/command_manager/file_resource.hpp @@ -30,12 +30,15 @@ struct FileResource: public Command std::string m_file_name; uint64_t m_interval; mutable uint64_t m_last_invoked; + + void read(); + +protected: std::string m_contents; void tryUpdate(); -protected: - virtual void read(); + virtual void onContentChange() {} public: bool needsFunction() const final { return false; } diff --git a/src/utils/command_manager/map_file_resource.cpp b/src/utils/command_manager/map_file_resource.cpp new file mode 100644 index 00000000000..48bc8c7d52d --- /dev/null +++ b/src/utils/command_manager/map_file_resource.cpp @@ -0,0 +1,142 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "utils/command_manager/map_file_resource.hpp" + +#include "utils/string_utils.hpp" + +namespace +{ + int readChar(const XMLNode* node, std::string&& name, char* value) + { + std::string temp(1, *value); + auto res = node->get(std::move(name), &temp); + temp = StringUtils::wideToUtf8(StringUtils::xmlDecode(temp)); + if (res == 0 || temp.empty()) + return 0; + + *value = temp[0]; + return 1; + } +} // namespace +//----------------------------------------------------------------------------- + +void MapFileResource::fromXmlNode(const XMLNode* node) +{ + FileResource::fromXmlNode(node); + node->get("print-format", &m_print_format); + readChar(node, "line-delimiter", &m_line_delimiter); + readChar(node, "field-delimiter", &m_field_delimiter); + + if (!readChar(node, "out-line-delimiter", &m_out_line_delimiter)) + m_out_line_delimiter = m_line_delimiter; + if (!readChar(node, "out-field-delimiter", &m_out_field_delimiter)) + m_out_field_delimiter = m_field_delimiter; + + readChar(node, "out-field-delimiter", &m_out_field_delimiter); + readChar(node, "left-quote", &m_left_quote); + readChar(node, "right-quote", &m_right_quote); + readChar(node, "escape", &m_escape); + node->get("index", &m_index); +} // MapFileResource +//----------------------------------------------------------------------------- + +void MapFileResource::onContentChange() +{ + auto rows = StringUtils::splitQuoted(m_contents, m_line_delimiter, m_left_quote, m_right_quote, m_escape); + m_rows.resize(rows.size()); + for (size_t i = 0; i < m_rows.size(); ++i) + { + m_rows[i] = std::make_shared(); + m_rows[i]->cells = StringUtils::splitQuoted(rows[i], m_field_delimiter, m_left_quote, m_right_quote, m_escape); + } + + m_fixer.clear(); + m_indexed.clear(); + if (m_index >= 0) + { + for (size_t i = 0; i < m_rows.size(); ++i) + { + auto& container = m_rows[i]; + if (m_index < (int)container->cells.size()) + { + const std::string key = container->cells[m_index]; + m_fixer.add(key); + m_indexed[key].push_back(container); + } + } + } +} // onContentChange +//----------------------------------------------------------------------------- + +void MapFileResource::execute(Context& context) +{ + tryUpdate(); + + auto& argv = context.m_argv; + auto acting_peer = context.actingPeer(); + auto peer = context.peer(); + + if (argv.size() < 2) + { + context.error(); + return; + } + + // I'm not sure if we should search by username. + // After all, the search column might contain something else. + if (argv[1] == "search") + { + if (2 >= argv.size()) + { + context.error(); + return; + } + + std::string key = argv[2]; + // Add fixing typos later + auto it = m_indexed.find(key); + if (it == m_indexed.end()) + context.say(StringUtils::insertValues("Nothing found for %s", key.c_str())); + else + { + std::string msg = ""; + int sz = it->second.size(); + if (sz >= 2) + msg += StringUtils::insertValues("Found %s lines\n", sz); + for (auto& row: it->second) + { + if (row) + msg += StringUtils::quoteEscapeArray( + row->cells.begin(), row->cells.end(), m_out_field_delimiter, + m_left_quote, m_right_quote, m_escape); + + msg += "\n"; + } + msg.pop_back(); + context.say(msg); + } + } + else + { + // Implement /command to and /command from to later + context.error(); + return; + } +} // execute +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/utils/command_manager/map_file_resource.hpp b/src/utils/command_manager/map_file_resource.hpp new file mode 100644 index 00000000000..e68088df710 --- /dev/null +++ b/src/utils/command_manager/map_file_resource.hpp @@ -0,0 +1,63 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef MAP_FILE_RESOURCE_HPP +#define MAP_FILE_RESOURCE_HPP + +#include "utils/types.hpp" +#include "utils/command_manager/file_resource.hpp" +#include + +struct MapFileResource: public FileResource +{ +private: + std::string m_print_format; + char m_line_delimiter = '\n'; // Line breaks should be checked on Windows + char m_field_delimiter = ' '; + char m_out_line_delimiter; + char m_out_field_delimiter; + char m_left_quote = '"'; + char m_right_quote = '"'; + char m_escape = '\\'; + + // Technically nothing prevents us from making several indices. + // Except the lack of practical need, of course. + int m_index = 0; + + struct Row + { + std::vector cells; + }; + + std::vector> m_rows; + std::map>> m_indexed; + + SetTypoFixer m_fixer; + +protected: + void onContentChange() override; + +public: + virtual void fromXmlNode(const XMLNode* node) override; + + virtual void execute(Context& context) override; +}; + + +#endif // MAP_FILE_RESOURCE_HPP \ No newline at end of file diff --git a/src/utils/set_typo_fixer.cpp b/src/utils/set_typo_fixer.cpp index 7f859e155f4..8d57966ba51 100644 --- a/src/utils/set_typo_fixer.cpp +++ b/src/utils/set_typo_fixer.cpp @@ -41,6 +41,13 @@ void SetTypoFixer::remove(const std::string& key) } // remove //----------------------------------------------------------------------------- +void SetTypoFixer::clear() +{ + m_set.clear(); + m_map.clear(); +} // clear +//----------------------------------------------------------------------------- + std::vector> SetTypoFixer::getClosest( const std::string& query, int count, bool case_sensitive) const { diff --git a/src/utils/set_typo_fixer.hpp b/src/utils/set_typo_fixer.hpp index 4105ce04512..8a2d14469ec 100644 --- a/src/utils/set_typo_fixer.hpp +++ b/src/utils/set_typo_fixer.hpp @@ -45,6 +45,7 @@ class SetTypoFixer void add(const std::string& key); void add(const std::string& key, const std::string& value); void remove(const std::string& key); + void clear(); std::vector> getClosest( const std::string& query, int count = 3, diff --git a/src/utils/string_utils.cpp b/src/utils/string_utils.cpp index af132c19aa6..efb811a0f52 100644 --- a/src/utils/string_utils.cpp +++ b/src/utils/string_utils.cpp @@ -254,6 +254,22 @@ namespace StringUtils return ans; } // quoteEscape + + + std::string quoteEscapeArray(const std::vector::iterator begin, + const std::vector::iterator end, + char c, char d, char e, char f) + { + std::string res = ""; + for (auto it = begin; it != end; ++it) + { + if (it != begin) { + res.push_back(c); + } + res += StringUtils::quoteEscape(*it, c, d, e, f); + } + return res; + } // quoteEscapeArray //------------------------------------------------------------------------- /** Splits a string into substrings separated by a certain character, and * returns a std::vector of all those substring. E.g.: diff --git a/src/utils/string_utils.hpp b/src/utils/string_utils.hpp index d83586468e1..c6f824d8ccb 100644 --- a/src/utils/string_utils.hpp +++ b/src/utils/string_utils.hpp @@ -58,6 +58,9 @@ namespace StringUtils char d, char e, char f); std::string quoteEscape(const std::string& s, char c, char d, char e, char f); + std::string quoteEscapeArray(const std::vector::iterator begin, + const std::vector::iterator end, + char c, char d, char e, char f); std::vector split(const std::string& s, char c, bool keepSplitChar=false); std::vector split(const std::u32string& s, char32_t c, From eec629045b7f244b75f4fe817ec7c6588881de0c Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Mon, 25 Aug 2025 22:47:29 +0400 Subject: [PATCH 6/9] Implement segment view of MapFileResource --- .../command_manager/map_file_resource.cpp | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/src/utils/command_manager/map_file_resource.cpp b/src/utils/command_manager/map_file_resource.cpp index 48bc8c7d52d..9fc5f1eccd8 100644 --- a/src/utils/command_manager/map_file_resource.cpp +++ b/src/utils/command_manager/map_file_resource.cpp @@ -98,6 +98,8 @@ void MapFileResource::execute(Context& context) return; } + std::string msg = ""; + // I'm not sure if we should search by username. // After all, the search column might contain something else. if (argv[1] == "search") @@ -115,7 +117,6 @@ void MapFileResource::execute(Context& context) context.say(StringUtils::insertValues("Nothing found for %s", key.c_str())); else { - std::string msg = ""; int sz = it->second.size(); if (sz >= 2) msg += StringUtils::insertValues("Found %s lines\n", sz); @@ -129,14 +130,54 @@ void MapFileResource::execute(Context& context) msg += "\n"; } msg.pop_back(); - context.say(msg); } } else { - // Implement /command to and /command from to later - context.error(); - return; + int from, to; + bool has_from = (1 < argv.size() && StringUtils::parseString(argv[1], &from) && from != 0); + bool has_to = (2 < argv.size() && StringUtils::parseString(argv[2], &to) && to != 0); + if (!has_from || (!has_to && 2 < argv.size())) + { + context.error(); + return; + } + if (!has_to) + to = (from > 0 ? 1 : -1); + + if (from >= to) + std::swap(from, to); + + if (from >= 0) // 3 8 -> [2, 8) + --from; + else if (to < 0) // -8 -3 -> [-8, -2) + ++to; + // else nothing: -4 5 -> [-4, 5) + + int n = m_indexed.size(); + int shift = (from / n) * n + (from < 0 ? -n : 0); + from -= shift; + to -= shift; + if (to - from > 0) + to = from + (to - from - 1) % n + 1; + + int sz = to - from; + if (sz >= 2) + msg += StringUtils::insertValues("Found %s lines\n", sz); + + for (int i = from; i < to; ++i) + { + int x = (i >= n ? i - n : i); + auto& row = m_rows[x]; + if (row) + msg += StringUtils::quoteEscapeArray( + row->cells.begin(), row->cells.end(), m_out_field_delimiter, + m_left_quote, m_right_quote, m_escape); + + msg += "\n"; + } + msg.pop_back(); } + context.say(msg); } // execute //----------------------------------------------------------------------------- \ No newline at end of file From 477bcb0d173dfb8a8a20e4b8c9481c4b1eb83e16 Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Fri, 29 Aug 2025 02:18:29 +0400 Subject: [PATCH 7/9] Minus includes, context.error() everywhere --- src/network/protocols/command_manager.cpp | 174 ++++++++++------------ src/network/protocols/command_manager.hpp | 1 - 2 files changed, 78 insertions(+), 97 deletions(-) diff --git a/src/network/protocols/command_manager.cpp b/src/network/protocols/command_manager.cpp index a173cb40881..cd6f2e4c33e 100644 --- a/src/network/protocols/command_manager.cpp +++ b/src/network/protocols/command_manager.cpp @@ -21,14 +21,10 @@ #include "addons/addon.hpp" #include "io/file_manager.hpp" #include "modes/soccer_world.hpp" -#include "network/database_connector.hpp" -#include "network/event.hpp" #include "network/game_setup.hpp" #include "network/network_player_profile.hpp" #include "network/protocols/server_lobby.hpp" -#include "network/server_config.hpp" #include "network/stk_host.hpp" -#include "network/stk_peer.hpp" #include "tracks/track.hpp" #include "tracks/track_manager.hpp" #include "utils/chat_manager.hpp" @@ -39,7 +35,6 @@ #include "utils/hourglass_reason.hpp" #include "utils/kart_elimination.hpp" #include "utils/lobby_asset_manager.hpp" -#include "utils/lobby_context.hpp" #include "utils/lobby_gp_manager.hpp" #include "utils/lobby_settings.hpp" #include "utils/lobby_queues.hpp" @@ -51,14 +46,6 @@ #include "utils/tournament.hpp" #include "utils/version.hpp" -#include -#include -#include -#include -#include -#include -#include - // TODO: kimden: should decorators use acting_peer? namespace @@ -73,13 +60,14 @@ namespace static const std::string g_type_soccer = "soccer"; const std::vector g_queue_names = { - "", "mqueue", "mcyclic", "mboth", - "kqueue", "qregular", "", "", - "kcyclic", "", "qcyclic", "", - "kboth", "", "", "qboth" + "", "mqueue", "mcyclic", "mboth", + "kqueue", "qregular", "", "", + "kcyclic", "", "qcyclic", "", + "kboth", "", "", "qboth" }; - enum QueueMask: int { + enum QueueMask: int + { QM_NONE = -1, QM_MAP_ONETIME = 1, QM_MAP_CYCLIC = 2, @@ -809,13 +797,6 @@ void CommandManager::update() } // update // ======================================================================== -void CommandManager::error(Context& context, bool is_error) -{ - // When you have no time, change all error(context) calls to context.error() - context.error(is_error); -} // error -// ======================================================================== - void CommandManager::execute(std::shared_ptr command, Context& context) { m_current_argv = context.m_argv; @@ -827,7 +808,7 @@ void CommandManager::execute(std::shared_ptr command, Context& context) catch (std::exception& ex) { // auto peer = context.m_peer.lock(); - error(context, true); + context.error(true); // // kimden: make error message better + add log // context.say(StringUtils::insertValues( @@ -862,7 +843,7 @@ void CommandManager::process_help(Context& context) } if (command == m_root_command) { - error(context); + context.error(); return; } context.say(command->getHelp()); @@ -1100,7 +1081,7 @@ void CommandManager::process_spectate(Context& context) if (argv.size() < 2 || !StringUtils::fromString(argv[1], value) || value < 0 || value > 2) { - error(context); + context.error(); return; } if (value >= 1) @@ -1272,7 +1253,7 @@ void CommandManager::process_checkaddon(Context& context) if (argv.size() < 2) { - error(context); + context.error(); return; } if (!validate(context, 1, TFT_ADDON_MAPS, false, true)) @@ -1388,7 +1369,7 @@ void CommandManager::process_id(Context& context) if (argv.size() < 2) { - error(context); + context.error(); return; } if (!validate(context, 1, TFT_ALL_MAPS, false, true)) @@ -1416,7 +1397,7 @@ void CommandManager::process_lsa(Context& context) (argv.size() == 2 && (argv[1].size() < 3 || has_options)) || (argv.size() == 3 && (!has_options || argv[2].size() < 3))) { - error(context); + context.error(); return; } std::string type = ""; @@ -1480,7 +1461,7 @@ void CommandManager::process_pha(Context& context) auto& argv = context.m_argv; if (argv.size() < 3) { - error(context); + context.error(); return; } @@ -1497,7 +1478,7 @@ void CommandManager::process_pha(Context& context) StringUtils::utf8ToWide(player_name)); if (player_name.empty() || !player_peer || addon_id.empty()) { - error(context); + context.error(); return; } @@ -1534,7 +1515,7 @@ void CommandManager::process_kick(Context& context) auto acting_peer = context.actingPeerMaybeNull(); if (argv.size() < 2) { - error(context); + context.error(); return; } @@ -1547,7 +1528,7 @@ void CommandManager::process_kick(Context& context) StringUtils::utf8ToWide(player_name)); if (player_name.empty() || !player_peer || player_peer->isAIPeer()) { - error(context); + context.error(); return; } if (player_peer->hammerLevel() > 0) @@ -1584,13 +1565,13 @@ void CommandManager::process_unban(Context& context) if (argv.size() < 2) { - error(context); + context.error(); return; } std::string player_name = argv[1]; if (player_name.empty()) { - error(context); + context.error(); return; } Log::info("CommandManager", "%s is now unbanned", player_name.c_str()); @@ -1606,13 +1587,13 @@ void CommandManager::process_ban(Context& context) auto& argv = context.m_argv; if (argv.size() < 2) { - error(context); + context.error(); return; } player_name = argv[1]; if (player_name.empty()) { - error(context); + context.error(); return; } Log::info("CommandManager", "%s is now banned", player_name.c_str()); @@ -1634,7 +1615,7 @@ void CommandManager::process_pas(Context& context) if (acting_peer->getPlayerProfiles().empty()) { Log::warn("CommandManager", "pas: no existing player profiles??"); - error(context); + context.error(); return; } player_name = acting_peer->getMainName(); @@ -1649,7 +1630,7 @@ void CommandManager::process_pas(Context& context) StringUtils::utf8ToWide(player_name)); if (player_name.empty() || !player_peer) { - error(context); + context.error(); return; } auto& scores = player_peer->getAddonsScores(); @@ -1766,7 +1747,7 @@ void CommandManager::process_sha(Context& context) auto& argv = context.m_argv; if (argv.size() != 2) { - error(context); + context.error(); return; } std::set total_addons; @@ -1797,7 +1778,7 @@ void CommandManager::process_mute(Context& context) if (argv.size() != 2 || argv[1].empty()) { - error(context); + context.error(); return; } @@ -1819,7 +1800,7 @@ void CommandManager::process_unmute(Context& context) if (argv.size() != 2 || argv[1].empty()) { - error(context); + context.error(); return; } @@ -1933,7 +1914,7 @@ void CommandManager::process_tell(Context& context) if (argv.size() == 1) { - error(context); + context.error(); return; } std::string ans; @@ -2003,7 +1984,7 @@ void CommandManager::process_to(Context& context) if (argv.size() == 1) { - error(context); + context.error(); return; } std::vector receivers; @@ -2035,7 +2016,7 @@ void CommandManager::process_record(Context& context) #ifdef ENABLE_SQLITE3 if (argv.size() < 5) { - error(context); + context.error(); return; } bool error = false; @@ -2122,7 +2103,7 @@ void CommandManager::process_length_multi(Context& context) if (argv.size() < 3 || !StringUtils::parseString(argv[2], &temp_double)) { - error(context); + context.error(); return; } double value = std::max(0.0, temp_double); @@ -2138,7 +2119,7 @@ void CommandManager::process_length_fixed(Context& context) if (argv.size() < 3 || !StringUtils::parseString(argv[2], &temp_int)) { - error(context); + context.error(); return; } int value = std::max(0, temp_int); @@ -2165,13 +2146,13 @@ void CommandManager::process_direction_assign(Context& context) auto& argv = context.m_argv; if (argv.size() < 2) { - error(context); + context.error(); return; } int temp_int = -1; if (!StringUtils::parseString(argv[1], &temp_int) || !getSettings()->setDirection(temp_int)) { - error(context); + context.error(); return; } Comm::sendStringToAllPeers(getSettings()->getDirectionAsString(true)); @@ -2207,7 +2188,7 @@ void CommandManager::process_queue_push(Context& context) if (argv.size() < 3) { - error(context); + context.error(); return; } int mask = get_queue_mask(argv[0]); @@ -2425,7 +2406,7 @@ void CommandManager::process_allowstart_assign(Context& context) // because of an extra cast. if (argv.size() == 1 || !(argv[1] == "0" || argv[1] == "1")) { - error(context); + context.error(); return; } getSettings()->setAllowedToStart(argv[1] != "0"); @@ -2445,7 +2426,7 @@ void CommandManager::process_shuffle_assign(Context& context) // Move validation to lobby settings. if (argv.size() == 1 || !(argv[1] == "0" || argv[1] == "1")) { - error(context); + context.error(); return; } getSettings()->setGPGridShuffled(argv[1] != "0"); @@ -2459,7 +2440,7 @@ void CommandManager::process_timeout(Context& context) auto& argv = context.m_argv; if (argv.size() < 2 || !StringUtils::parseString(argv[1], &seconds) || seconds <= 0) { - error(context); + context.error(); return; } getLobby()->setTimeoutFromNow(seconds); @@ -2474,7 +2455,7 @@ void CommandManager::process_team(Context& context) auto& argv = context.m_argv; if (argv.size() != 3) { - error(context); + context.error(); return; } if (!validate(context, 2, TFT_PRESENT_USERS, false, true)) @@ -2508,7 +2489,7 @@ void CommandManager::process_swapteams(Context& context) if (argv.size() != 2) { - error(context); + context.error(); return; } // todo move list of teams and checking teams to another unit later, @@ -2600,12 +2581,13 @@ void CommandManager::process_randomteams(Context& context) void CommandManager::process_resetgp(Context& context) { auto& argv = context.m_argv; - if (argv.size() >= 2) { + if (argv.size() >= 2) + { int number_of_games; if (!StringUtils::parseString(argv[1], &number_of_games) || number_of_games <= 0) { - error(context); + context.error(); return; } getGameSetupFromCtx()->setGrandPrixTrack(number_of_games); @@ -2624,7 +2606,7 @@ void CommandManager::process_cat(Context& context) { if (argv.size() != 3) { - error(context); + context.error(); return; } std::string category = argv[1]; @@ -2639,7 +2621,7 @@ void CommandManager::process_cat(Context& context) { if (argv.size() != 3) { - error(context); + context.error(); return; } std::string category = argv[1]; @@ -2660,7 +2642,7 @@ void CommandManager::process_cat(Context& context) if (argv.size() != 3 || !StringUtils::parseString(argv[2], &displayed) || displayed < -1 || displayed > 1) { - error(context); + context.error(); return; } } @@ -2681,7 +2663,7 @@ void CommandManager::process_vip(Context& context) { if (argv.size() > 2) { - error(context); + context.error(); return; } if (argv.size() == 2) @@ -2701,7 +2683,7 @@ void CommandManager::process_vip(Context& context) { if (argv.size() != 2) { - error(context); + context.error(); return; } if (!validate(context, 1, TFT_PRESENT_USERS, false, true)) @@ -2718,7 +2700,7 @@ void CommandManager::process_vip(Context& context) { if (argv.size() != 2) { - error(context); + context.error(); return; } if (!validate(context, 1, TFT_PRESENT_USERS, false, true)) @@ -2753,7 +2735,7 @@ void CommandManager::process_troll_assign(Context& context) auto& argv = context.m_argv; if (argv.size() == 1 || !(argv[1] == "0" || argv[1] == "1")) { - error(context); + context.error(); return; } auto hit_processor = getHitProcessor(); @@ -2788,7 +2770,7 @@ void CommandManager::process_hitmsg_assign(Context& context) auto& argv = context.m_argv; if (argv.size() == 1 || !(argv[1] == "0" || argv[1] == "1")) { - error(context); + context.error(); return; } auto hit_processor = getHitProcessor(); @@ -2823,7 +2805,7 @@ void CommandManager::process_teamhit_assign(Context& context) auto& argv = context.m_argv; if (argv.size() == 1 || !(argv[1] == "0" || argv[1] == "1")) { - error(context); + context.error(); return; } auto hit_processor = getHitProcessor(); @@ -2896,7 +2878,7 @@ void CommandManager::process_muteall(Context& context) auto tournament = getTournament(); if (!tournament) { - error(context, true); + context.error(true); return; } if (!acting_peer->hasPlayerProfiles()) @@ -2926,7 +2908,7 @@ void CommandManager::process_game(Context& context) auto tournament = getTournament(); if (!tournament) { - error(context, true); + context.error(true); return; } @@ -3001,12 +2983,12 @@ void CommandManager::process_role(Context& context) auto tournament = getTournament(); if (!tournament) { - error(context, true); + context.error(true); return; } if (argv.size() < 3) { - error(context); + context.error(); return; } if (argv[1].length() > argv[2].length()) @@ -3021,7 +3003,7 @@ void CommandManager::process_role(Context& context) (argv[3] == "p" || argv[3] == "permanent")); if (role.length() != 1) { - error(context); + context.error(); return; } char role_char = role[0]; @@ -3142,7 +3124,7 @@ void CommandManager::process_stop(Context& context) { if (!getTournament()) { - error(context, true); + context.error(true); return; } World* w = World::getWorld(); @@ -3159,7 +3141,7 @@ void CommandManager::process_go(Context& context) { if (!getTournament()) { - error(context, true); + context.error(true); return; } World* w = World::getWorld(); @@ -3176,7 +3158,7 @@ void CommandManager::process_lobby(Context& context) { if (!getTournament()) { - error(context, true); + context.error(true); return; } World* w = World::getWorld(); @@ -3193,7 +3175,7 @@ void CommandManager::process_init(Context& context) auto& argv = context.m_argv; if (!getTournament()) { - error(context, true); + context.error(true); return; } int red, blue; @@ -3201,7 +3183,7 @@ void CommandManager::process_init(Context& context) !StringUtils::parseString(argv[1], &red) || !StringUtils::parseString(argv[2], &blue)) { - error(context); + context.error(); return; } World* w = World::getWorld(); @@ -3243,7 +3225,7 @@ void CommandManager::process_test(Context& context) argv.resize(4, ""); if (argv[2] == "no" && argv[3] == "u") { - error(context); + context.error(); return; } if (context.m_voting) @@ -3287,7 +3269,7 @@ void CommandManager::process_slots_assign(Context& context) fail = true; if (fail) { - error(context); + context.error(); return; } if (context.m_voting) @@ -3348,7 +3330,7 @@ void CommandManager::process_preserve_assign(Context& context) std::string msg = ""; if (argv.size() != 3) { - error(context); + context.error(); return; } if (argv[2] == "0") @@ -3372,7 +3354,7 @@ void CommandManager::process_history(Context& context) auto tournament = getTournament(); if (!tournament) { - error(context, true); + context.error(true); return; } std::string msg = "Map history:"; @@ -3389,19 +3371,19 @@ void CommandManager::process_history_assign(Context& context) auto tournament = getTournament(); if (!tournament) { - error(context, true); + context.error(true); return; } std::string msg = ""; if (argv.size() != 3) { - error(context); + context.error(); return; } int index; if (!StringUtils::fromString(argv[1], index) || index < 0) { - error(context); + context.error(); return; } if (!validate(context, 2, TFT_ALL_MAPS, false, false)) @@ -3409,7 +3391,7 @@ void CommandManager::process_history_assign(Context& context) std::string id = argv[2]; if (!tournament->assignToHistory(index, id)) { - error(context); + context.error(); return; } @@ -3432,13 +3414,13 @@ void CommandManager::process_voting_assign(Context& context) std::string msg = ""; if (argv.size() < 2) { - error(context); + context.error(); return; } int value; if (!StringUtils::fromString(argv[1], value) || value < 0 || value > 1) { - error(context); + context.error(); return; } getMapVoteHandler()->setAlgorithm(value); @@ -3459,7 +3441,7 @@ void CommandManager::process_why_hourglass(Context& context) if (acting_peer->getPlayerProfiles().empty()) { Log::warn("CommandManager", "whyhourglass: no existing player profiles??"); - error(context); + context.error(); return; } player_name = acting_peer->getMainName(); @@ -3474,7 +3456,7 @@ void CommandManager::process_why_hourglass(Context& context) StringUtils::utf8ToWide(player_name)); if (player_name.empty() || !player_peer) { - error(context); + context.error(); return; } @@ -3501,7 +3483,7 @@ void CommandManager::process_available_teams_assign(Context& context) if (argv.size() < 2) { - error(context); + context.error(); return; } std::string value = ""; @@ -3552,14 +3534,14 @@ void CommandManager::process_cooldown_assign(Context& context) if (argv.size() < 2) { - error(context); + context.error(); return; } int new_cooldown = -1; // kimden: figure out what's with epsilons in STK if (!StringUtils::parseString(argv[1], &new_cooldown) || new_cooldown < 0) { - error(context); + context.error(); return; } getSettings()->setLobbyCooldown(new_cooldown); @@ -3589,7 +3571,7 @@ void CommandManager::process_net(Context& context) if (acting_peer->getPlayerProfiles().empty()) { Log::warn("CommandManager", "net: no existing player profiles??"); - error(context); + context.error(); return; } player_name = acting_peer->getMainName(); @@ -3604,7 +3586,7 @@ void CommandManager::process_net(Context& context) StringUtils::utf8ToWide(player_name)); if (player_name.empty() || !player_peer) { - error(context); + context.error(); return; } @@ -3678,7 +3660,7 @@ void CommandManager::process_temp250318(Context& context) int value = 0; if (argv.size() < 2 || !StringUtils::parseString(argv[1], &value)) { - error(context); + context.error(); return; } auto settings = getSettings(); diff --git a/src/network/protocols/command_manager.hpp b/src/network/protocols/command_manager.hpp index 2706d4d22f7..410204ab530 100644 --- a/src/network/protocols/command_manager.hpp +++ b/src/network/protocols/command_manager.hpp @@ -119,7 +119,6 @@ class CommandManager: public LobbyContextComponent void vote(Context& context, std::string category, std::string value); void update(); - void error(Context& context, bool is_error = false); void execute(std::shared_ptr command, Context& context); From e78d79dad4ae6ab1a7950e7d095816167535638d Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Fri, 29 Aug 2025 03:13:56 +0400 Subject: [PATCH 8/9] Minor queue command improvements (not all of them) --- src/network/protocols/command_manager.cpp | 216 +++++++++++----------- 1 file changed, 109 insertions(+), 107 deletions(-) diff --git a/src/network/protocols/command_manager.cpp b/src/network/protocols/command_manager.cpp index cd6f2e4c33e..62bd40ff0f5 100644 --- a/src/network/protocols/command_manager.cpp +++ b/src/network/protocols/command_manager.cpp @@ -86,7 +86,7 @@ namespace return i; return QueueMask::QM_NONE; } // get_queue_mask - // ==================================================================== + //------------------------------------------------------------------------- std::string get_queue_name(int x) { @@ -101,7 +101,7 @@ namespace return StringUtils::insertValues( "[Error QN%d: please report with /tell about it] queue", x); } // get_queue_name - // ==================================================================== + //------------------------------------------------------------------------- int another_cyclic_queue(int x) { @@ -111,7 +111,18 @@ namespace // return QM_KART_CYCLIC; return QM_NONE; } // another_cyclic_queue - // ==================================================================== + //------------------------------------------------------------------------- + + template + void forAllQueuesInMask(int mask, Fn&& fn) + { + for (int x = QM_START; x < QM_END; x <<= 1) + { + if (mask & x) + fn(x); + } + } // forAllQueuesInMask + //------------------------------------------------------------------------- void restoreCmdByArgv(std::string& cmd, std::vector& argv, char c, char d, char e, char f, @@ -120,7 +131,7 @@ namespace cmd = StringUtils::quoteEscapeArray(argv.begin() + from, argv.end(), c, d, e, f); } // restoreCmdByArgv - // ======================================================================== + //------------------------------------------------------------------------- // Auxiliary things, should be moved somewhere because they just help @@ -211,7 +222,8 @@ void CommandManager::initCommandsInfo() const std::string name = c->getShortName(); - if (current == root2 && command == m_root_command && used_commands.find(name) != used_commands.end()) + if (current == root2 && command == m_root_command + && used_commands.find(name) != used_commands.end()) continue; else if (current == root) used_commands.insert(name); @@ -222,9 +234,11 @@ void CommandManager::initCommandsInfo() dfs(node, c); } }; + dfs(root, m_root_command); if (root2) dfs(root2, m_root_command); + delete root; delete root2; } // initCommandsInfo @@ -2163,18 +2177,19 @@ void CommandManager::process_queue(Context& context) { std::string msg = ""; int mask = get_queue_mask(context.m_argv[0]); - for (int x = QM_START; x < QM_END; x <<= 1) + + forAllQueuesInMask(mask, [&](int x) { - if (mask & x) - { - auto& queue = get_queue(x); - msg += StringUtils::insertValues("%s (size = %d):", - get_queue_name(x), (int)queue.size()); - for (std::shared_ptr& s: queue) - msg += " " + s->toString(); - msg += "\n"; - } - } + auto& queue = get_queue(x); + + msg += StringUtils::insertValues("%s (size = %d):", + get_queue_name(x), (int)queue.size()); + + for (std::shared_ptr& s: queue) + msg += " " + s->toString(); + msg += "\n"; + }); + msg.pop_back(); context.say(msg); } // process_queue @@ -2242,31 +2257,23 @@ void CommandManager::process_queue_push(Context& context) std::string msg = ""; - for (int x = QM_START; x < QM_END; x <<= 1) + forAllQueuesInMask(mask, [&](int x) { - if (mask & x) - { - if (mask & QM_ALL_KART_QUEUES) - { - // TODO Make sure to update the next branch too; unite them somehow? - add_to_queue(x, mask, to_front, filter_text); - } - else - { - // TODO Make sure to update the previous branch too; unite them somehow? - add_to_queue(x, mask, to_front, filter_text); - } + // TODO: Make sure to update both branches if at all + if (mask & QM_ALL_KART_QUEUES) + add_to_queue(x, mask, to_front, filter_text); + else + add_to_queue(x, mask, to_front, filter_text); - msg += StringUtils::insertValues( - "Pushed { %s } to the %s of %s, current queue size: %d", - filter_text.c_str(), - (to_front ? "front" : "back"), - get_queue_name(x).c_str(), - get_queue(x).size() - ); - msg += "\n"; - } - } + msg += StringUtils::insertValues( + "Pushed { %s } to the %s of %s, current queue size: %d", + filter_text.c_str(), + (to_front ? "front" : "back"), + get_queue_name(x).c_str(), + get_queue(x).size() + ); + msg += "\n"; + }); msg.pop_back(); Comm::sendStringToAllPeers(msg); @@ -2282,42 +2289,41 @@ void CommandManager::process_queue_pop(Context& context) int mask = get_queue_mask(argv[0]); bool from_back = (argv[1] == "pop_back"); - for (int x = QM_START; x < QM_END; x <<= 1) + forAllQueuesInMask(mask, [&](int x) { - if (mask & x) + int another = another_cyclic_queue(x); + + if (get_queue(x).empty()) { - int another = another_cyclic_queue(x); - if (get_queue(x).empty()) { - msg += "The " + get_queue_name(x) + " was empty before.\n"; - } - else - { - auto object = (from_back ? get_queue(x).back() : get_queue(x).front()); - msg += "Popped " + object->toString() - + " from the " + (from_back ? "back" : "front") + " of the " - + get_queue_name(x) + ","; - if (from_back) - { - get_queue(x).pop_back(); - } - else - { - get_queue(x).pop_front(); - } - if (another >= QM_START && !(mask & another)) - { - // here you have to pop from FRONT and not back because it - // was pushed to the front - see process_queue_push - auto& q = get_queue(another); - if (!q.empty() && q.front()->isPlaceholder()) - q.pop_front(); - } - msg += " current queue size: " - + std::to_string(get_queue(x).size()); - msg += "\n"; - } + msg += StringUtils::insertValues( + "The %s was empty before.\n", get_queue_name(x).c_str()); + return; } - } + + auto object = (from_back ? get_queue(x).back() : get_queue(x).front()); + msg += StringUtils::insertValues("Popped %s from the %s of the %s,", + object->toString().c_str(), + (from_back ? "back" : "front"), + get_queue_name(x).c_str() + ); + + if (from_back) + get_queue(x).pop_back(); + else + get_queue(x).pop_front(); + + if (another >= QM_START && !(mask & another)) + { + // here you have to pop from FRONT and not back because it + // was pushed to the front - see process_queue_push + auto& q = get_queue(another); + if (!q.empty() && q.front()->isPlaceholder()) + q.pop_front(); + } + msg += StringUtils::insertValues(" current queue size: %s\n", + get_queue(x).size()); + }); + msg.pop_back(); Comm::sendStringToAllPeers(msg); getLobby()->updatePlayerList(); @@ -2328,24 +2334,22 @@ void CommandManager::process_queue_clear(Context& context) { int mask = get_queue_mask(context.m_argv[0]); std::string msg = ""; - for (int x = QM_START; x < QM_END; x <<= 1) + forAllQueuesInMask(mask, [&](int x) { - if (mask & x) - { - int another = another_cyclic_queue(x); - msg += StringUtils::insertValues( - "The " + get_queue_name(x) + " is now empty (previous size: %d)", - (int)get_queue(x).size()) + "\n"; - get_queue(x).clear(); + int another = another_cyclic_queue(x); - if (another >= QM_START && !(mask & another)) - { - auto& q = get_queue(another); - while (!q.empty() && q.front()->isPlaceholder()) - q.pop_front(); - } + msg += StringUtils::insertValues( + "The " + get_queue_name(x) + " is now empty (previous size: %d)", + (int)get_queue(x).size()) + "\n"; + get_queue(x).clear(); + + if (another >= QM_START && !(mask & another)) + { + auto& q = get_queue(another); + while (!q.empty() && q.front()->isPlaceholder()) + q.pop_front(); } - } + }); msg.pop_back(); Comm::sendStringToAllPeers(msg); getLobby()->updatePlayerList(); @@ -2364,28 +2368,26 @@ void CommandManager::process_queue_shuffle(Context& context) // we don't have to do anything with placeholders for the corresponding // cyclic queue, BUT we have to not shuffle the placeholders in the // current queue itself - for (int x = QM_START; x < QM_END; x <<= 1) - { - if (mask & x) + forAllQueuesInMask(mask, [&](int x) + { + auto& queue = get_queue(x); + // As the placeholders can be only at the start of a queue, + // let's just do a binary search - in case there are many placeholders + int L = -1; + int R = queue.size(); + int mid; + while (R - L > 1) { - auto& queue = get_queue(x); - // As the placeholders can be only at the start of a queue, - // let's just do a binary search - in case there are many placeholders - int L = -1; - int R = queue.size(); - int mid; - while (R - L > 1) - { - mid = (L + R) / 2; - if (queue[mid]->isPlaceholder()) - L = mid; - else - R = mid; - } - std::shuffle(queue.begin() + R, queue.end(), g); - msg += "The " + get_queue_name(x) + " is now shuffled\n"; + mid = (L + R) / 2; + if (queue[mid]->isPlaceholder()) + L = mid; + else + R = mid; } - } + std::shuffle(queue.begin() + R, queue.end(), g); + msg += "The " + get_queue_name(x) + " is now shuffled\n"; + }); + msg.pop_back(); Comm::sendStringToAllPeers(msg); getLobby()->updatePlayerList(); From e976bba0fb7c737f12116f752aa80e5d3c00669f Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Sat, 30 Aug 2025 03:35:19 +0400 Subject: [PATCH 9/9] Move restoreCmdFromArgv to StringUtils I'll make a structure for chars later, as well as bigger changes that I planned to do but didn't want to confuse them together --- src/network/protocols/command_manager.cpp | 28 +++++++++++++---------- src/utils/lobby_queues.hpp | 3 --- src/utils/string_utils.cpp | 14 ++++++++++-- src/utils/string_utils.hpp | 8 +++++-- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/network/protocols/command_manager.cpp b/src/network/protocols/command_manager.cpp index 62bd40ff0f5..d494d235a2a 100644 --- a/src/network/protocols/command_manager.cpp +++ b/src/network/protocols/command_manager.cpp @@ -124,13 +124,13 @@ namespace } // forAllQueuesInMask //------------------------------------------------------------------------- - void restoreCmdByArgv(std::string& cmd, + void restoreCmdFromArgv(std::string& cmd, std::vector& argv, char c, char d, char e, char f, int from = 0) { cmd = StringUtils::quoteEscapeArray(argv.begin() + from, argv.end(), c, d, e, f); - } // restoreCmdByArgv + } // restoreCmdFromArgv //------------------------------------------------------------------------- @@ -531,7 +531,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) argv = StringUtils::splitQuoted(cmd, ' ', '"', '"', '\\'); if (argv.empty()) return; - restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); permissions = getLobby()->getPermissions(peer); voting = false; @@ -554,7 +554,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) argv = StringUtils::splitQuoted(cmd, ' ', '"', '"', '\\'); if (argv.empty()) return; - restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); voting = m_user_saved_voting[username]; target_peer = m_user_saved_acting_peer[username]; target_peer_strong = target_peer.lock(); @@ -728,7 +728,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) { std::string new_cmd = p.first + " " + p.second; auto new_argv = StringUtils::splitQuoted(new_cmd, ' ', '"', '"', '\\'); - restoreCmdByArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); std::string msg2 = StringUtils::insertValues( "Command \"/%s\" has been successfully voted", new_cmd.c_str()); @@ -789,7 +789,7 @@ void CommandManager::update() { std::string new_cmd = p.first + " " + p.second; auto new_argv = StringUtils::splitQuoted(new_cmd, ' ', '"', '"', '\\'); - restoreCmdByArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); std::string msg = StringUtils::insertValues( "Command \"/%s\" has been successfully voted", new_cmd.c_str()); @@ -2216,7 +2216,7 @@ void CommandManager::process_queue_push(Context& context) argv[2] = asset_manager->getRandomAddonMap(); std::string filter_text = ""; - restoreCmdByArgv(filter_text, argv, ' ', '"', '"', '\\', 2); + restoreCmdFromArgv(filter_text, argv, ' ', '"', '"', '\\', 2); // Fix typos only if track queues are used (majority of cases anyway) // TODO: I don't know how to fix typos for both karts and tracks @@ -2837,7 +2837,7 @@ void CommandManager::process_scoring_assign(Context& context) auto& argv = context.m_argv; std::string cmd2; - restoreCmdByArgv(cmd2, argv, ' ', '"', '"', '\\', 1); + StringUtils::restoreCmdFromArgv(cmd2, argv, ' ', '"', '"', '\\', 1); if (getGPManager()->trySettingGPScoring(cmd2)) Comm::sendStringToAllPeers("Scoring set to \"" + cmd2 + "\""); else @@ -3734,13 +3734,16 @@ bool CommandManager::hasTypo(std::shared_ptr acting_peer, std::shared_p { if (!acting_peer.get()) // voted return false; + std::string username = ""; if (peer->hasPlayerProfiles()) username = peer->getMainName(); + auto it = m_user_last_correct_argument.find(username); if (it != m_user_last_correct_argument.end() && std::make_pair(idx, subidx) <= it->second) return false; + std::string text = argv[idx]; std::string prefix = ""; std::string suffix = ""; @@ -3750,6 +3753,7 @@ bool CommandManager::hasTypo(std::shared_ptr acting_peer, std::shared_p suffix = text.substr(substr_r); text = text.substr(substr_l, substr_r - substr_l); } + auto closest_commands = stf.getClosest(text, top, case_sensitive); if (closest_commands.empty()) { @@ -3786,12 +3790,12 @@ bool CommandManager::hasTypo(std::shared_ptr acting_peer, std::shared_p } for (unsigned i = 0; i < closest_commands.size(); ++i) { argv[idx] = prefix + closest_commands[i].first + suffix; - restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); m_user_command_replacements[username].push_back(cmd); response += "\ntype /" + std::to_string(i + 1) + " to choose \"" + closest_commands[i].first + "\""; } argv[idx] = initial_argument; - restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); Comm::sendStringToPeer(peer, response); return true; } @@ -3799,7 +3803,7 @@ bool CommandManager::hasTypo(std::shared_ptr acting_peer, std::shared_p if (!dont_replace) { argv[idx] = prefix + closest_commands[0].first + suffix; // converts case or regex - restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); } return false; } // hasTypo @@ -3891,7 +3895,7 @@ void CommandManager::shift(std::string& cmd, std::vector& argv, m_user_last_correct_argument[username].first -= count; - restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + StringUtils::restoreCmdFromArgv(cmd, argv, ' ', '"', '"', '\\'); } // shift //----------------------------------------------------------------------------- diff --git a/src/utils/lobby_queues.hpp b/src/utils/lobby_queues.hpp index 7b88d2ba05f..dc94fb34ac1 100644 --- a/src/utils/lobby_queues.hpp +++ b/src/utils/lobby_queues.hpp @@ -49,11 +49,8 @@ class LobbyQueues: public LobbyContextComponent private: Queue m_onetime_tracks_queue; - Queue m_cyclic_tracks_queue; - Queue m_onetime_karts_queue; - Queue m_cyclic_karts_queue; // Temporary reference getters diff --git a/src/utils/string_utils.cpp b/src/utils/string_utils.cpp index efb811a0f52..175cb6182b1 100644 --- a/src/utils/string_utils.cpp +++ b/src/utils/string_utils.cpp @@ -256,8 +256,8 @@ namespace StringUtils } // quoteEscape - std::string quoteEscapeArray(const std::vector::iterator begin, - const std::vector::iterator end, + std::string quoteEscapeArray(const std::vector::const_iterator begin, + const std::vector::const_iterator end, char c, char d, char e, char f) { std::string res = ""; @@ -271,6 +271,16 @@ namespace StringUtils return res; } // quoteEscapeArray //------------------------------------------------------------------------- + + void restoreCmdFromArgv(std::string& cmd, + const std::vector& argv, + char c, char d, char e, char f, + int from) + { + cmd = quoteEscapeArray(argv.begin() + from, argv.end(), c, d, e, f); + } // restoreCmdFromArgv + //------------------------------------------------------------------------- + /** Splits a string into substrings separated by a certain character, and * returns a std::vector of all those substring. E.g.: * split("a b=c d=e",' ') --> ["a", "b=c", "d=e"] diff --git a/src/utils/string_utils.hpp b/src/utils/string_utils.hpp index c6f824d8ccb..8d5eb791629 100644 --- a/src/utils/string_utils.hpp +++ b/src/utils/string_utils.hpp @@ -58,9 +58,13 @@ namespace StringUtils char d, char e, char f); std::string quoteEscape(const std::string& s, char c, char d, char e, char f); - std::string quoteEscapeArray(const std::vector::iterator begin, - const std::vector::iterator end, + std::string quoteEscapeArray(const std::vector::const_iterator begin, + const std::vector::const_iterator end, char c, char d, char e, char f); + void restoreCmdFromArgv(std::string& cmd, + const std::vector& argv, + char c, char d, char e, char f, + int from = 0); std::vector split(const std::string& s, char c, bool keepSplitChar=false); std::vector split(const std::u32string& s, char32_t c,