/
Mr.Stalin
/
FOnline-Engine
Обзор
Документация
Войти
/
Mr.Stalin
/
FOnline-Engine
Код
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Source/Server/DataBase-Json.cpp
282 строки
10 KB
cvet
Alloc fixes (#192)
27 июл 2026, 14:30
Не верифицирован
27 июл 2026, 14:30
ba3760e
Код
Авторство
О чём код?
#include "DataBase.h" FO_DISABLE_WARNINGS_PUSH() #include <bson/bson.h> #include <json.hpp> FO_DISABLE_WARNINGS_POP() #include "WinApiUndef.inc" FO_BEGIN_NAMESPACE class DbJson final : public DataBaseImpl { public: DbJson(const DbJson&) = delete; DbJson(DbJson&&) noexcept = delete; auto operator=(const DbJson&) = delete; auto operator=(DbJson&&) noexcept = delete; explicit DbJson(ptr<DataBaseSettings> db_settings, string_view storage_dir, DataBasePanicCallback panic_callback) : DataBaseImpl(db_settings, std::move(panic_callback)), _storageDir {storage_dir}, _jsonIndent {db_settings->JsonIndent} { fs_create_directories(storage_dir); StartCommitThread(); } ~DbJson() override { StopCommitThread(); } protected: [[nodiscard]] auto GetStringKeyEscaping() const noexcept -> DataBaseStringKeyEscaping override { return DataBaseStringKeyEscaping::File; } void EnsureCollection(hstring collection_name, DataBaseKeyType key_type) override { ignore_unused(key_type); scoped_lock locker {_storageLocker}; string dir = strex("{}/{}", _storageDir, collection_name).str(); if (!fs_create_directories(dir)) { throw DataBaseException("DbJson Can't ensure collection directory", dir); } } [[nodiscard]] auto GetAllRecordIds(hstring collection_name) const -> vector<DataBaseKey> override { FO_STACK_TRACE_ENTRY(); scoped_lock locker {_storageLocker}; auto key_type = GetCollectionKeyType(collection_name); vector<DataBaseKey> ids; std::error_code ec; auto dir_path = std::filesystem::path {fs_make_path(strex(_storageDir).combine_path(collection_name))}; auto dir_iterator = std::filesystem::directory_iterator(dir_path, ec); if (!ec) { for (const auto& dir_entry : dir_iterator) { if (dir_entry.is_directory()) { continue; } auto path_str = dir_entry.path().filename().u8string(); string path = string(path_str.begin(), path_str.end()); if (strex(path).get_file_extension() != "json") { continue; } string key_str = strvex(path).extract_file_name().erase_file_extension().str(); if (key_type == DataBaseKeyType::IntId) { if (!strvex(key_str).is_number()) { throw DataBaseException("DbJson invalid numeric key format", key_str); } int64_t id_value = strvex(key_str).to_int64(); if (id_value <= 0) { throw DataBaseException("DbJson invalid numeric key value", key_str); } ids.emplace_back(ident_t {id_value}); } else { ids.emplace_back(key_str); } } } return ids; } protected: [[nodiscard]] auto GetRecord(hstring collection_name, const DataBaseKey& id) const -> AnyData::Document override { FO_STACK_TRACE_ENTRY(); scoped_lock locker {_storageLocker}; string path = strex("{}/{}/{}.json", _storageDir, collection_name, FormatJsonStorageDbKey(id, GetCollectionKeyType(collection_name))); auto json = fs_read_file(path); if (!json) { return {}; } bson_t bson; bson_error_t error; if (!bson_init_from_json(&bson, json->c_str(), numeric_cast<ssize_t>(json->length()), &error)) { throw DataBaseException("DbJson bson_init_from_json", path); } AnyData::Document doc; BsonToDocument(&bson, doc); bson_destroy(&bson); return doc; } void InsertRecord(hstring collection_name, const DataBaseKey& id, const AnyData::Document& doc) override { FO_STACK_TRACE_ENTRY(); FO_VERIFY_AND_THROW(!doc.Empty(), "JSON database insert received an empty document", collection_name, id); scoped_lock locker {_storageLocker}; string path = strex("{}/{}/{}.json", _storageDir, collection_name, FormatJsonStorageDbKey(id, GetCollectionKeyType(collection_name))); if (fs_exists(path)) { throw DataBaseException("DbJson File exists for inserting", path); } bson_t bson; bson_init(&bson); DocumentToBson(doc, &bson); size_t length = 0; auto json_lookup = make_nptr(bson_as_canonical_extended_json(&bson, &length)); if (!json_lookup) { throw DataBaseException("DbJson bson_as_canonical_extended_json", path); } auto json = make_unique_del_ptr(json_lookup, [](ptr<char> text) FO_DEFERRED { bson_free(text.get()); }); bson_destroy(&bson); auto pretty_json = nlohmann::json::parse(json.get()); auto pretty_json_dump = pretty_json.dump(_jsonIndent > 0 ? _jsonIndent : -1); string dir = strex(path).extract_dir().str(); if (!dir.empty() && !fs_create_directories(dir)) { throw DataBaseException("DbJson Can't open file", path); } string tmp_path = strex("{}.tmp", path).str(); if (!fs_write_file(tmp_path, pretty_json_dump)) { fs_remove_file(tmp_path); throw DataBaseException("DbJson Can't write file", path); } if (!fs_rename(tmp_path, path)) { fs_remove_file(tmp_path); throw DataBaseException("DbJson Can't commit file", path); } } void UpdateRecord(hstring collection_name, const DataBaseKey& id, const AnyData::Document& doc) override { FO_STACK_TRACE_ENTRY(); FO_VERIFY_AND_THROW(!doc.Empty(), "JSON database update received an empty document", collection_name, id); scoped_lock locker {_storageLocker}; string path = strex("{}/{}/{}.json", _storageDir, collection_name, FormatJsonStorageDbKey(id, GetCollectionKeyType(collection_name))); auto json = fs_read_file(path); if (!json) { throw DataBaseException("DbJson Can't open file for reading", path); } bson_t bson; bson_error_t error; if (!bson_init_from_json(&bson, json->c_str(), numeric_cast<ssize_t>(json->length()), &error)) { throw DataBaseException("DbJson bson_init_from_json", path); } DocumentToBson(doc, &bson); size_t new_length = 0; auto new_json_lookup = make_nptr(bson_as_canonical_extended_json(&bson, &new_length)); if (!new_json_lookup) { throw DataBaseException("DbJson bson_as_canonical_extended_json", path); } auto new_json = make_unique_del_ptr(new_json_lookup, [](ptr<char> text) FO_DEFERRED { bson_free(text.get()); }); bson_destroy(&bson); auto pretty_json = nlohmann::json::parse(new_json.get()); auto pretty_json_dump = pretty_json.dump(_jsonIndent > 0 ? _jsonIndent : -1); string dir = strex(path).extract_dir().str(); if (!dir.empty() && !fs_create_directories(dir)) { throw DataBaseException("DbJson Can't open file for writing", path); } string tmp_path = strex("{}.tmp", path).str(); if (!fs_write_file(tmp_path, pretty_json_dump)) { fs_remove_file(tmp_path); throw DataBaseException("DbJson Can't write file", path); } if (!fs_rename(tmp_path, path)) { fs_remove_file(tmp_path); throw DataBaseException("DbJson Can't commit file", path); } } void DeleteRecord(hstring collection_name, const DataBaseKey& id) override { FO_STACK_TRACE_ENTRY(); scoped_lock locker {_storageLocker}; string path = strex("{}/{}/{}.json", _storageDir, collection_name, FormatJsonStorageDbKey(id, GetCollectionKeyType(collection_name))); if (!fs_remove_file(path)) { throw DataBaseException("DbJson Can't delete file", path); } } private: static auto FormatJsonStorageDbKey(const DataBaseKey& key, DataBaseKeyType key_type) -> string { if (GetDbKeyType(key) != key_type) { throw DataBaseException("DbJson invalid key type", key_type == DataBaseKeyType::IntId ? "Id" : "String"); } return std::visit( [key_type](const auto& value) -> string { using T = std::decay_t<decltype(value)>; if constexpr (std::is_same_v<T, ident_t>) { FO_VERIFY_AND_THROW(key_type == DataBaseKeyType::IntId, "JSON database key expected a numeric identifier but the collection key type differs", value); FO_VERIFY_AND_THROW(value != ident_t {}, "JSON database key cannot encode an empty identifier"); return strex("{}", value).str(); } else { FO_VERIFY_AND_THROW(key_type == DataBaseKeyType::String, "JSON database key expected a string identifier but the collection key type differs", value); FO_VERIFY_AND_THROW(!value.empty(), "JSON database key cannot encode an empty string identifier"); return value; } }, key); } mutable mutex _storageLocker {}; string _storageDir {}; int32_t _jsonIndent {}; }; auto CreateJsonDataBase(ptr<DataBaseSettings> db_settings, string_view storage_dir, DataBasePanicCallback panic_callback) -> unique_ptr<DataBaseImpl> { InitializeBsonMemory(); return SafeAlloc::MakeUnique<DbJson>(db_settings, storage_dir, std::move(panic_callback)); } FO_END_NAMESPACE