diff --git a/core/cache/cache.cpp b/core/cache/cache.cpp index 6157da9..9ab2479 100644 --- a/core/cache/cache.cpp +++ b/core/cache/cache.cpp @@ -22,6 +22,8 @@ #include "cache.h" Cache::Cache(){ + std::filesystem::create_directory("data"); + for (const auto& row: std::filesystem::directory_iterator("data")){ if (row.path().extension() != ".dat") continue; @@ -61,40 +63,34 @@ Cache::Cache(){ } } -std::string Cache::get(std::string db, std::string key){ - if (cache_.find(db) != cache_.end()) { - if (cache_[db].find(key) != cache_[db].end()){ - return cache_[db][key].value; +std::string Cache::get(const std::string& db, const std::string& key) noexcept { + auto data = cache_.find(db); + if (data != cache_.end()) { + auto value = data->second.find(key); + if (value != data->second.end()){ + return value->second.value; } } return ""; } -bool Cache::set(std::string db, std::string key, std::string value, int expire){ - - CacheStruct data; - data.value = value; - data.expire = expire; - - cache_[db][key] = data; +bool Cache::set(const std::string& db, const std::string& key, const std::string& value, int expire) noexcept { + CacheStruct data{std::move(value), expire}; + cache_[db].insert_or_assign(key, std::move(data)); + isChange = true; return true; } -bool Cache::del(std::string db, std::string key){ - if (cache_.find(db) != cache_.end()) { - if (cache_[db].find(key) != cache_[db].end()){ - cache_[db].erase(key); - - return true; - } - } +bool Cache::del(const std::string& db, const std::string& key) noexcept { + cache_[db].erase(key); + isChange = true; return false; } -std::vector Cache::keys(std::string db){ +std::vector Cache::keys(const std::string& db) noexcept { std::vector keys; for (const auto& row: cache_[db]){ @@ -104,43 +100,47 @@ std::vector Cache::keys(std::string db){ return keys; } -void Cache::save(){ - std::filesystem::create_directory("data"); - - size_t cacheSize = cache_.size(); +void Cache::save() noexcept { + if (isChange) { + std::filesystem::create_directory("data"); + + size_t cacheSize = cache_.size(); + + for (const auto& row: cache_) { + const std::string& key = row.first; + const std::unordered_map& map = row.second; - for (const auto& row: cache_) { - const std::string& key = row.first; - const std::unordered_map& map = row.second; + std::string filename = "data/" + key + ".dat"; + std::ofstream file(filename, std::ios::binary); - std::string filename = "data/" + key + ".dat"; - std::ofstream file(filename, std::ios::binary); + file.write(reinterpret_cast(&cacheSize), sizeof(cacheSize)); - file.write(reinterpret_cast(&cacheSize), sizeof(cacheSize)); + size_t keySize = key.size(); + file.write(reinterpret_cast(&keySize), sizeof(keySize)); + file.write(key.data(), keySize); - size_t keySize = key.size(); - file.write(reinterpret_cast(&keySize), sizeof(keySize)); - file.write(key.data(), keySize); + size_t mapSize = map.size(); + file.write(reinterpret_cast(&mapSize), sizeof(mapSize)); - size_t mapSize = map.size(); - file.write(reinterpret_cast(&mapSize), sizeof(mapSize)); + for (const auto& rowPair: map) { + const std::string& pairKey = rowPair.first; + const CacheStruct& cachedata = rowPair.second; - for (const auto& rowPair: map) { - const std::string& pairKey = rowPair.first; - const CacheStruct& cachedata = rowPair.second; + size_t pairKeySize = pairKey.size(); + file.write(reinterpret_cast(&pairKeySize), sizeof(pairKeySize)); + file.write(pairKey.data(), pairKeySize); - size_t pairKeySize = pairKey.size(); - file.write(reinterpret_cast(&pairKeySize), sizeof(pairKeySize)); - file.write(pairKey.data(), pairKeySize); + cachedata.serialize(file); + } - cachedata.serialize(file); + file.close(); } - file.close(); + isChange = false; } } -void Cache::expire(){ +void Cache::expire() noexcept { std::thread([this]() { while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -155,4 +155,9 @@ void Cache::expire(){ } } }).detach(); +} + +Cache::~Cache(){ + isChange = true; + save(); } \ No newline at end of file diff --git a/core/cache/cache.h b/core/cache/cache.h index 832488d..4b9a70c 100644 --- a/core/cache/cache.h +++ b/core/cache/cache.h @@ -53,15 +53,17 @@ class Cache { public: Cache (); - std::string get(std::string db, std::string key); - bool set(std::string db, std::string key, std::string value, int expire = -1); - bool del(std::string db, std::string key); - std::vector keys (std::string db); - void save(); - void expire(); + std::string get(const std::string& db, const std::string& key) noexcept; + bool set(const std::string& db, const std::string& key, const std::string& value, int expire = -1) noexcept; + bool del(const std::string& db, const std::string& key) noexcept; + std::vector keys(const std::string& db) noexcept; + void save() noexcept; + void expire() noexcept; + ~Cache(); private: std::unordered_map> cache_; + bool isChange = false; }; #endif \ No newline at end of file diff --git a/core/manager/manager.cpp b/core/manager/manager.cpp index 42684b6..8344f98 100644 --- a/core/manager/manager.cpp +++ b/core/manager/manager.cpp @@ -21,210 +21,178 @@ #include "manager.h" -Manager::Manager(boost::asio::ip::tcp::socket socket, Cache& cache, std::function onDisconnect) - : socket_(std::move(socket)), cache_(cache), onDisconnect_(onDisconnect) {} +Manager::Manager(boost::asio::ip::tcp::socket socket, Cache& cache, std::function onDisconnect, ConfigConnect& ConfigConn) + : socket_(std::move(socket)), cache_(cache), onDisconnect_(onDisconnect), ConfigConn_(ConfigConn) {} Manager::~Manager() { onDisconnect_(); } -void Manager::run(){ +void Manager::run() noexcept { read(); } -void Manager::read(){ +void Manager::read() noexcept { auto self(shared_from_this()); - std::string remoteIp = socket_.remote_endpoint().address().to_string(); - - auto findIP = std::find(ConfigConn.auth.allow_ip.begin(), ConfigConn.auth.allow_ip.end(), remoteIp); - if (findIP != ConfigConn.auth.allow_ip.end() || ConfigConn.auth.allow_ip.size() == 0) { - boost::asio::async_read_until(socket_, boost::asio::dynamic_buffer(data_), "\n", - [this, self](boost::system::error_code ec, std::size_t length){ - if (!ec) { - invokeAction(); - read(); - } + + boost::asio::async_read_until(socket_, boost::asio::dynamic_buffer(data_), "\r\n", + [this, self](boost::system::error_code ec, std::size_t length) { + if (ec) return; + data_.erase(0, data_.find_first_not_of("\r\n")); + if (!data_.empty()) invokeAction(); + data_.clear(); + read(); } - ); - }else{ - result(std::string("ERROR: IP is not on the released list")); - socket_.close(); - } + ); } -void Manager::result(std::string value){ - auto self(shared_from_this()); +void Manager::result(std::string value) noexcept { boost::asio::async_write(socket_, boost::asio::buffer(value), - [this, self](boost::system::error_code ec, std::size_t){ - if (ec) socket_.close(); - } - ); + [this](boost::system::error_code ec, std::size_t){}); } -void Manager::invokeAction(){ +void Manager::invokeAction() noexcept { std::istringstream request(data_); - std::string command; - std::string value; std::vector args; + std::string command; + + request >> command; + boost::algorithm::to_upper(command); + + if (std::find(commands.all.begin(), commands.all.end(), command) == commands.all.end()) return; + char quote = '\0'; - while (request >> std::ws) { + std::string value; + + while (request) { char peekStream = request.peek(); if (peekStream == '"' || peekStream == '\'') { quote = request.get(); - - char c; value.clear(); + char c; + bool escape = false; + while (request.get(c)) { - if (c == '\\') { - char nextChar; - request.get(nextChar); - value.push_back(nextChar); - } else if (c == quote) { - break; - } else { + if (escape) { value.push_back(c); + escape = false; } + else if (c == '\\') escape = true; + else if (c == quote) break; + else value.push_back(c); + } + } else if (std::isspace(peekStream)) { + request.ignore(); + if (!value.empty()) { + args.push_back(std::move(value)); + value.clear(); } } else { request >> value; + if (request) { + args.push_back(std::move(value)); + value.clear(); + } } - - args.push_back(value); } - command = args[0]; - args.erase(std::remove(args.begin(), args.end(), command), args.end()); + if (!value.empty()) args.push_back(std::move(value)); - if (std::find(commands.auth.begin(), commands.auth.end(), command) != commands.auth.end()) { - invokeAuth(args); - } + if (commands.auth == command) return invokeAuth(args); - if (std::find(commands.use.begin(), commands.use.end(), command) != commands.use.end()) { - invokeUse(args); - } - - if (std::find(commands.get.begin(), commands.get.end(), command) != commands.get.end()) { - invokeGet(args); - } + if (user.user.empty() && user.db.empty()) return result("ERROR: you are not authenticated"); + if (commands.use == command) return invokeUse(args); - if (std::find(commands.set.begin(), commands.set.end(), command) != commands.set.end()) { - invokeSet(args); - } + if (user.db == "*") return result("ERROR: use the USE DB command to choose the name of the database instance"); + if (commands.get == command) return invokeGet(args); + if (commands.set == command) return invokeSet(args); + if (commands.del == command) return invokeDel(args); + if (commands.keys == command) return invokeKeys(args); - if (std::find(commands.del.begin(), commands.del.end(), command) != commands.del.end()) { - invokeDel(args); - } - - if (std::find(commands.keys.begin(), commands.keys.end(), command) != commands.keys.end()) { - invokeKeys(args); - } - - data_.clear(); + return result("ERROR: incorrect command"); } -void Manager::invokeDel(std::vector args){ - if (user.user.empty() && user.db.empty()) return result(std::string("ERROR: you need to authenticate")); - if (user.db == "*") return result(std::string("ERROR: use the USE DB command to choose the name of the database instance")); - if (args[0].empty()) return result(std::string("ERROR: use DEL KEY")); +void Manager::invokeDel(std::vector args) noexcept { + if (args[0].empty()) return result("ERROR: use DEL KEY"); if (!cache_.del(user.db, args[0])){ - return result(std::string("ERROR: there is no record for this key")); + return result("ERROR: there is no record for this key"); } - scheduleSave(); - - result(std::string("SUCCESS: data deleted successfully")); + result("SUCCESS: data deleted successfully"); } -void Manager::invokeSet(std::vector args){ - if (user.user.empty() && user.db.empty()) return result(std::string("ERROR: you need to authenticate")); - if (user.db == "*") return result(std::string("ERROR: use the USE DB command to choose the name of the database instance")); - if (args[0].empty() || args[1].empty()) return result(std::string("ERROR: use SET KEY VALUE (EXPIRE)")); +void Manager::invokeSet(std::vector args) { + if (args.size() < 2 || args[0].empty() || args[1].empty()) return result("ERROR: use SET KEY VALUE (EXPIRE)"); int expire = -1; - if (args.size() > 2 && !args[2].empty()){ - try { - expire = std::stoi(args[2]); - }catch (std::exception& e){ - std::cout << "Failed: " << e.what() << std::endl; + if (args.size() > 2 && !args[2].empty()) { + const char* begin = args[2].c_str(); + const char* end = begin + args[2].size(); + auto [ptr, ec] = std::from_chars(begin, end, expire); + + if (ec != std::errc() || ptr != end) { + return result("ERROR: invalid expire time format"); } } - if (!cache_.set(user.db, args[0], args[1], expire)){ - return result(std::string("ERROR: failed to enter data")); + if (!cache_.set(user.db, args[0], args[1], expire)) { + return result("ERROR: failed to enter data"); } - scheduleSave(); + return result("SUCCESS: data saved successfully"); - result(std::string("SUCCESS: data saved successfully")); } -void Manager::invokeGet(std::vector args){ - if (user.user.empty() && user.db.empty()) return result(std::string("ERROR: you need to authenticate")); - if (user.db == "*") return result(std::string("ERROR: use the `USE DB` command to choose the name of the database instance")); - if (args[0].empty()) return result(std::string("ERROR: use GET KEY")); +void Manager::invokeGet(std::vector args) noexcept { + if (args[0].empty()) return result("ERROR: use GET KEY"); std::string value = cache_.get(user.db, args[0]); if (value.empty()){ - return result(std::string("ERROR: there is no record for this key")); + return result("ERROR: there is no record for this key"); } - result(std::string("SUCCESS: " + value)); + result("SUCCESS: " + value); } -void Manager::invokeUse(std::vector args){ - if (user.user.empty() && user.db.empty()) return result(std::string("ERROR: you need to authenticate")); - if (args[0].empty()) return result(std::string("ERROR: use the `USE DB` command to choose the name of the database instance")); +void Manager::invokeUse(std::vector args) noexcept { + if (args[0].empty()) return result("ERROR: use the `USE DB` command to choose the name of the database instance"); - for (const auto& auth : ConfigConn.auth.basic) { + for (const auto& auth : ConfigConn_.auth.basic) { if (auth.user == user.user && auth.db == "*"){ user.db = args[0]; - return result(std::string("SUCCESS: you have successfully changed the database")); + return result("SUCCESS: you have successfully changed the database"); } } - return result(std::string("ERROR: you are not allowed to do this")); + result("ERROR: you are not allowed to do this"); } -void Manager::invokeAuth(std::vector args){ - if (!user.user.empty() && !user.db.empty()) return result(std::string("ERROR: you are authenticated")); - if (args[0].empty() || args[1].empty()) return result(std::string("ERROR: use AUTH USER PASS")); +void Manager::invokeAuth(std::vector args) noexcept { + if (args[0].empty() || args[1].empty()) return result("ERROR: use AUTH USER PASS"); - for (const auto& auth : ConfigConn.auth.basic) { + for (const auto& auth : ConfigConn_.auth.basic) { if (auth.user == args[0] && auth.pass == args[1]){ user.user = auth.user; user.db = auth.db; - return result(std::string("SUCCESS: authenticated")); + return result("SUCCESS: authenticated"); } } - result(std::string("ERROR: failed to authenticate")); + result("ERROR: failed to authenticate"); } -void Manager::invokeKeys(std::vector args){ - if (user.user.empty() && user.db.empty()) return result(std::string("ERROR: you need to authenticate")); - if (user.db == "*") return result(std::string("ERROR: use the `USE DB` command to choose the name of the database instance")); - +void Manager::invokeKeys(std::vector args) noexcept { std::string messageKeys; std::vector keys = cache_.keys(user.db); for (const std::string& key : keys) { messageKeys += key + "\n"; } - return result(std::string("SUCCESS: \r\n" + messageKeys)); + return result("SUCCESS: \r\n" + messageKeys); } -void Manager::scheduleSave(){ - if (!saveRunning_){ - saveRunning_ = true; - std::thread([this]() { - std::this_thread::sleep_for(std::chrono::seconds(10)); - cache_.save(); - saveRunning_ = false; - }).detach(); - } -} \ No newline at end of file diff --git a/core/manager/manager.h b/core/manager/manager.h index b6b972b..075f617 100644 --- a/core/manager/manager.h +++ b/core/manager/manager.h @@ -29,47 +29,50 @@ #include #include #include + #include + #include #include #include + #include #include "../entities/user.h" #include "../entities/config.h" struct ManagerCommands { - std::vector set = {"SET", "set"}; - std::vector del = {"DEL", "del"}; - std::vector get = {"GET", "get"}; - std::vector auth = {"AUTH", "auth"}; - std::vector use = {"USE", "use"}; - std::vector keys = {"KEYS", "keys"}; + std::string set = "SET"; + std::string del = "DEL"; + std::string get = "GET"; + std::string auth = "AUTH"; + std::string use = "USE"; + std::string keys = "KEYS"; + std::vector all = {"SET", "DEL", "GET", "AUTH", "USE", "KEYS"}; }; class Manager: public std::enable_shared_from_this { public: - Manager(boost::asio::ip::tcp::socket socket, Cache& cache, std::function onDisconnect); - void run(); + Manager(boost::asio::ip::tcp::socket socket, Cache& cache, std::function onDisconnect, ConfigConnect& ConfigConn); + void run() noexcept; ~Manager(); private: - void read(); - void result(std::string value); - void invokeAction(); - void invokeDel(std::vector args); + void read() noexcept; + void result(std::string value) noexcept; + void invokeAction() noexcept; + void invokeDel(std::vector args) noexcept; void invokeSet(std::vector args); - void invokeGet(std::vector args); - void invokeAuth(std::vector args); - void invokeUse(std::vector args); - void invokeKeys(std::vector args); - void scheduleSave(); + void invokeGet(std::vector args) noexcept; + void invokeAuth(std::vector args) noexcept; + void invokeUse(std::vector args) noexcept; + void invokeKeys(std::vector args) noexcept; - boost::asio::ip::tcp::socket socket_; - std::string data_; Cache& cache_; - ManagerCommands commands; + std::string data_; UserEntities user; - ConfigConnect ConfigConn = Config::getConfigConnect(); - std::function onDisconnect_; + ManagerCommands commands; bool saveRunning_ = false; + std::function onDisconnect_; + boost::asio::ip::tcp::socket socket_; + ConfigConnect& ConfigConn_; }; #endif \ No newline at end of file diff --git a/core/socket/socket.cpp b/core/socket/socket.cpp index dc924a1..22dfcca 100644 --- a/core/socket/socket.cpp +++ b/core/socket/socket.cpp @@ -24,38 +24,48 @@ CoreSocket::CoreSocket(boost::asio::io_context& io_context, std::string ip, short port, Cache& cache_) : acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(ip), port)), cache_(cache_), client_(0), socket_(io_context) { + acceptor_.set_option(boost::asio::socket_base::reuse_address(true)); + acceptor_.set_option(boost::asio::ip::tcp::no_delay(true)); accept(); ping(); } -void CoreSocket::accept() { +void CoreSocket::accept() noexcept { acceptor_.async_accept( - [this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) { - if (!ec) { - if (client_ < ConfigConn.max_clients) { + [this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) { + if (!ec) { + if (client_ > ConfigConn.max_clients) { + boost::asio::write(socket, boost::asio::buffer("ERROR: maximum connections reached")); + return; + } + + std::string remoteIp = socket.remote_endpoint().address().to_string(); + auto findIP = std::find(ConfigConn.auth.allow_ip.begin(), ConfigConn.auth.allow_ip.end(), remoteIp); + if (findIP == ConfigConn.auth.allow_ip.end() && ConfigConn.auth.allow_ip.size() > 0) { + boost::asio::write(socket, boost::asio::buffer("ERROR: IP is not on the released list")); + return; + } + ++client_; std::make_shared(std::move(socket), cache_, [this](){ --client_; - })->run(); - } else { - std::string message = "ERROR: maximum connections reached"; - boost::asio::write(socket, boost::asio::buffer(message)); - } - } + }, ConfigConn)->run(); - accept(); + accept(); + } } ); } -void CoreSocket::ping() { +void CoreSocket::ping() noexcept { std::thread([this](){ while (true) { if (socket_.is_open()) { boost::asio::write(socket_, boost::asio::buffer(std::string("PING\r\n"))); } - std::this_thread::sleep_for(std::chrono::seconds(5)); + cache_.save(); + std::this_thread::sleep_for(std::chrono::seconds(15)); } }).detach(); } diff --git a/core/socket/socket.h b/core/socket/socket.h index c06c3d6..a7f1c9d 100644 --- a/core/socket/socket.h +++ b/core/socket/socket.h @@ -36,8 +36,8 @@ CoreSocket(boost::asio::io_context& io_context, std::string, short port, Cache& cache_); private: - void accept(); - void ping(); + void accept() noexcept; + void ping() noexcept; Cache& cache_; std::atomic client_; diff --git a/main.cpp b/main.cpp index 7b0c6b1..368bfe7 100644 --- a/main.cpp +++ b/main.cpp @@ -35,7 +35,5 @@ int main(){ std::cerr << "Exception: " << e.what() << "\n"; } - cache_.save(); - return 0; } \ No newline at end of file