Updating the deploy algorithm
This commit is contained in:
parent
72abd7e20e
commit
3dea4906e1
@ -61,6 +61,8 @@ find_package(Opus REQUIRED)
|
|||||||
find_package(spdlog REQUIRED)
|
find_package(spdlog REQUIRED)
|
||||||
find_package(Jemalloc REQUIRED)
|
find_package(Jemalloc REQUIRED)
|
||||||
find_package(Protobuf REQUIRED)
|
find_package(Protobuf REQUIRED)
|
||||||
|
message("${zstd_DIR}")
|
||||||
|
find_package(zstd REQUIRED)
|
||||||
|
|
||||||
include_directories(${StringVariable_INCLUDE_DIR})
|
include_directories(${StringVariable_INCLUDE_DIR})
|
||||||
add_subdirectory(music/)
|
add_subdirectory(music/)
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 0dc8e28125f9727f2dffef4b6f93b397099db1f0
|
Subproject commit ef2d6842d547b8f84ebbe4fe9fa6f132bcd84d35
|
@ -296,6 +296,7 @@ target_link_libraries(TeaSpeakServer
|
|||||||
|
|
||||||
jsoncpp_lib
|
jsoncpp_lib
|
||||||
${ed25519_LIBRARIES_STATIC}
|
${ed25519_LIBRARIES_STATIC}
|
||||||
|
zstd::libzstd_static
|
||||||
)
|
)
|
||||||
|
|
||||||
if (COMPILE_WEB_CLIENT)
|
if (COMPILE_WEB_CLIENT)
|
||||||
|
@ -13,38 +13,42 @@ using namespace ts::permission;
|
|||||||
|
|
||||||
//#define DISABLE_CACHING
|
//#define DISABLE_CACHING
|
||||||
|
|
||||||
|
struct ts::server::CachedPermissionManager {
|
||||||
|
ServerId server_id{0};
|
||||||
|
ClientDbId client_database_id{0};
|
||||||
|
std::weak_ptr<permission::v2::PermissionManager> instance{};
|
||||||
|
|
||||||
|
std::shared_ptr<permission::v2::PermissionManager> instance_ref{}; /* reference to the current instance, will be refreshed every time the instance gets accessed */
|
||||||
|
std::chrono::time_point<std::chrono::system_clock> last_access{};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct ts::server::StartupCacheEntry {
|
||||||
|
ServerId sid{0};
|
||||||
|
|
||||||
|
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions{};
|
||||||
|
std::deque<std::unique_ptr<StartupPropertyEntry>> properties{};
|
||||||
|
};
|
||||||
|
|
||||||
DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {}
|
DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {}
|
||||||
DatabaseHelper::~DatabaseHelper() {
|
DatabaseHelper::~DatabaseHelper() {
|
||||||
for(const auto& elm : cachedPermissionManagers)
|
this->cached_permission_managers.clear();
|
||||||
delete elm;
|
|
||||||
cachedPermissionManagers.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseHelper::tick() {
|
void DatabaseHelper::tick() {
|
||||||
|
auto cache_timeout = std::chrono::system_clock::now() - std::chrono::minutes{10};
|
||||||
{
|
{
|
||||||
threads::MutexLock l(this->permManagerLock);
|
std::lock_guard cp_lock{this->cached_permission_manager_lock};
|
||||||
auto cpy = this->cachedPermissionManagers;
|
|
||||||
for(const auto& mgr : cpy){
|
|
||||||
//if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) //TODO remove instand delete!
|
|
||||||
mgr->ownLock.reset();
|
|
||||||
if(mgr->manager.expired()){
|
|
||||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), mgr));
|
|
||||||
delete mgr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const std::unique_ptr<CachedPermissionManager>& manager) {
|
||||||
threads::MutexLock l(this->propsLock);
|
if(manager->last_access < cache_timeout)
|
||||||
auto pcpy = this->cachedProperties;
|
manager->instance_ref = nullptr;
|
||||||
for(const auto& mgr : pcpy) {
|
|
||||||
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5))
|
if(manager->instance.expired())
|
||||||
mgr->ownLock.reset();
|
return true;
|
||||||
if(mgr->properties.expired()) {
|
|
||||||
this->cachedProperties.erase(std::find(this->cachedProperties.begin(), this->cachedProperties.end(), mgr));
|
return false;
|
||||||
delete mgr;
|
}), this->cached_permission_managers.end());
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,15 +140,12 @@ bool DatabaseHelper::validClientDatabaseId(const std::shared_ptr<VirtualServer>&
|
|||||||
void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
||||||
auto serverId = (ServerId) (server ? server->getServerId() : 0);
|
auto serverId = (ServerId) (server ? server->getServerId() : 0);
|
||||||
{
|
{
|
||||||
lock_guard<threads::Mutex> lock(permManagerLock);
|
lock_guard lock{cached_permission_manager_lock};
|
||||||
for(auto permMgr : this->cachedPermissionManagers)
|
|
||||||
if(serverId == 0 || (permMgr->cldbid == cldbid && permMgr->sid == serverId)) {
|
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
|
||||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
|
return entry->server_id == serverId && entry->client_database_id == cldbid;
|
||||||
delete permMgr;
|
}), this->cached_permission_managers.end());
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
//TODO remove from props cache?
|
|
||||||
|
|
||||||
sql::result state{};
|
sql::result state{};
|
||||||
|
|
||||||
@ -236,26 +237,38 @@ inline sql::result load_permissions_v2(
|
|||||||
logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
|
logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
|
||||||
}
|
}
|
||||||
|
|
||||||
#define UPDATE_COMMAND "UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
|
constexpr static std::string_view kPermissionUpdateCommand{"UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
|
||||||
#define INSERT_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"
|
constexpr static std::string_view kPermissionInsertCommand{"INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"};
|
||||||
#define DELETE_COMMAND "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
|
constexpr static std::string_view kPermissionDeleteCommand{"DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
|
||||||
|
|
||||||
|
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::find_cached_permission_manager(ServerId server_id,
|
||||||
|
ClientDbId client_database_id) {
|
||||||
|
for(auto it = this->cached_permission_managers.begin(); it != this->cached_permission_managers.end(); it++) {
|
||||||
|
auto& cached_manager = *it;
|
||||||
|
|
||||||
|
if(cached_manager->client_database_id == client_database_id && cached_manager->server_id == server_id) {
|
||||||
|
auto manager = cached_manager->instance.lock();
|
||||||
|
if(!manager){
|
||||||
|
this->cached_permission_managers.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cached_manager->last_access = system_clock::now();
|
||||||
|
cached_manager->instance_ref = manager;
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
||||||
auto server_id = server ? server->getServerId() : 0;
|
auto server_id = server ? server->getServerId() : 0;
|
||||||
#ifndef DISABLE_CACHING
|
#ifndef DISABLE_CACHING
|
||||||
{
|
{
|
||||||
lock_guard<threads::Mutex> lock(permManagerLock);
|
std::lock_guard lock{cached_permission_manager_lock};
|
||||||
for(auto permMgr : this->cachedPermissionManagers)
|
auto manager = this->find_cached_permission_manager(server_id, cldbid);
|
||||||
if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)) {
|
if(manager) return manager;
|
||||||
auto ptr = permMgr->manager.lock();
|
|
||||||
if(!ptr){
|
|
||||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
|
|
||||||
delete permMgr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
permMgr->lastAccess = system_clock::now();
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -298,15 +311,23 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
|
|||||||
|
|
||||||
|
|
||||||
#ifndef DISABLE_CACHING
|
#ifndef DISABLE_CACHING
|
||||||
this->permManagerLock.lock();
|
auto cache_entry = std::make_unique<CachedPermissionManager>();
|
||||||
auto entry = new CachedPermissionManager();
|
cache_entry->server_id = server_id;
|
||||||
entry->sid = server_id;
|
cache_entry->instance = permission_manager;
|
||||||
entry->manager = permission_manager;
|
cache_entry->instance_ref = permission_manager;
|
||||||
entry->ownLock = permission_manager;
|
cache_entry->client_database_id = cldbid;
|
||||||
entry->cldbid = cldbid;
|
cache_entry->last_access = system_clock::now();
|
||||||
entry->lastAccess = system_clock::now();
|
|
||||||
this->cachedPermissionManagers.push_back(entry);
|
{
|
||||||
this->permManagerLock.unlock();
|
std::lock_guard cache_lock{this->cached_permission_manager_lock};
|
||||||
|
|
||||||
|
/* test if we might not got a second instance */
|
||||||
|
auto manager = this->find_cached_permission_manager(server_id, cldbid);
|
||||||
|
if(manager) return manager;
|
||||||
|
|
||||||
|
this->cached_permission_managers.push_back(std::move(cache_entry));
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
return permission_manager;
|
return permission_manager;
|
||||||
}
|
}
|
||||||
@ -319,7 +340,7 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
|
|||||||
|
|
||||||
auto server_id = server ? server->getServerId() : 0;
|
auto server_id = server ? server->getServerId() : 0;
|
||||||
for(auto& update : updates) {
|
for(auto& update : updates) {
|
||||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||||
|
|
||||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||||
@ -386,7 +407,7 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::Virt
|
|||||||
|
|
||||||
auto server_id = server ? server->getServerId() : 0;
|
auto server_id = server ? server->getServerId() : 0;
|
||||||
for(auto& update : updates) {
|
for(auto& update : updates) {
|
||||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||||
|
|
||||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||||
@ -458,7 +479,7 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
|
|||||||
|
|
||||||
auto server_id = server ? server->getServerId() : 0;
|
auto server_id = server ? server->getServerId() : 0;
|
||||||
for(auto& update : updates) {
|
for(auto& update : updates) {
|
||||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||||
|
|
||||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||||
@ -524,7 +545,7 @@ void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::Vi
|
|||||||
|
|
||||||
auto server_id = server ? server->getServerId() : 0;
|
auto server_id = server ? server->getServerId() : 0;
|
||||||
for(auto& update : updates) {
|
for(auto& update : updates) {
|
||||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||||
|
|
||||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||||
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
|
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
|
||||||
@ -1058,6 +1079,15 @@ void DatabaseHelper::clearStartupCache(ts::ServerId sid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DatabaseHelper::handleServerDelete(ServerId server_id) {
|
||||||
|
{
|
||||||
|
std::lock_guard pm_lock{this->cached_permission_manager_lock};
|
||||||
|
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
|
||||||
|
return entry->server_id == server_id;
|
||||||
|
}), this->cached_permission_managers.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`
|
//SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`
|
||||||
//SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`
|
//SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`
|
||||||
struct StartupPermissionArgument {
|
struct StartupPermissionArgument {
|
||||||
@ -1247,3 +1277,81 @@ bool DatabaseHelper::deletePlaylist(const std::shared_ptr<ts::server::VirtualSer
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr static auto kDBListQuery{R"(
|
||||||
|
SELECT `clients`.*, `properties`.`value` as `client_description` FROM (
|
||||||
|
SELECT
|
||||||
|
`clients_server`.`client_database_id`,
|
||||||
|
`clients_server`.`client_unique_id`,
|
||||||
|
`clients_server`.`client_nickname`,
|
||||||
|
`clients_server`.`client_ip`,
|
||||||
|
`clients_server`.`client_created`,
|
||||||
|
`clients_server`.`client_last_connected`,
|
||||||
|
`clients_server`.`client_total_connections`,
|
||||||
|
`clients`.`client_login_name` FROM `clients_server`
|
||||||
|
INNER JOIN `clients` ON `clients`.`client_database_id` = `clients_server`.`client_database_id`
|
||||||
|
WHERE `server_id` = :serverId LIMIT :offset, :limit
|
||||||
|
) AS `clients`
|
||||||
|
LEFT JOIN `properties` ON `properties`.serverId = :serverId AND `properties`.key = 'client_description' AND `properties`.`id` = `clients`.`client_database_id`
|
||||||
|
)"};
|
||||||
|
|
||||||
|
void DatabaseHelper::listDatabaseClients(
|
||||||
|
ServerId server_id,
|
||||||
|
const std::optional<int64_t>& offset,
|
||||||
|
const std::optional<int64_t>& limit,
|
||||||
|
void (* callback)(void *, const DatabaseClient &),
|
||||||
|
void *user_argument) {
|
||||||
|
|
||||||
|
DatabaseClient client;
|
||||||
|
size_t set_index{0};
|
||||||
|
auto sqlResult = sql::command{this->sql, kDBListQuery,
|
||||||
|
variable{":serverId", server_id},
|
||||||
|
variable{":offset", offset.has_value() ? *offset : 0},
|
||||||
|
variable{":limit", limit.has_value() ? *limit : -1}
|
||||||
|
}.query([&](int length, std::string* values, std::string* names) {
|
||||||
|
set_index++;
|
||||||
|
|
||||||
|
auto index{0};
|
||||||
|
try {
|
||||||
|
assert(names[index] == "client_database_id");
|
||||||
|
client.client_database_id = std::stoull(values[index++]);
|
||||||
|
|
||||||
|
assert(names[index] == "client_unique_id");
|
||||||
|
client.client_unique_id = values[index++];
|
||||||
|
|
||||||
|
assert(names[index] == "client_nickname");
|
||||||
|
client.client_nickname = values[index++];
|
||||||
|
|
||||||
|
assert(names[index] == "client_ip");
|
||||||
|
client.client_ip = values[index++];
|
||||||
|
|
||||||
|
assert(names[index] == "client_created");
|
||||||
|
client.client_last_connected = values[index++];
|
||||||
|
|
||||||
|
assert(names[index] == "client_last_connected");
|
||||||
|
client.client_created = values[index++];
|
||||||
|
|
||||||
|
assert(names[index] == "client_total_connections");
|
||||||
|
client.client_total_connections = values[index++];
|
||||||
|
|
||||||
|
assert(names[index] == "client_login_name");
|
||||||
|
client.client_login_name = values[index++];
|
||||||
|
|
||||||
|
assert(names[index] == "client_description");
|
||||||
|
client.client_description = values[index++];
|
||||||
|
|
||||||
|
assert(index == length);
|
||||||
|
} catch (std::exception& ex) {
|
||||||
|
logError(server_id, "Failed to parse client base properties at index {}: {}. Offset: {} Limit: {} Set: {}",
|
||||||
|
index - 1,
|
||||||
|
ex.what(),
|
||||||
|
offset.has_value() ? std::to_string(*offset) : "not given",
|
||||||
|
limit.has_value() ? std::to_string(*limit) : "not given",
|
||||||
|
set_index - 1
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(user_argument, client);
|
||||||
|
});
|
||||||
|
}
|
@ -25,26 +25,6 @@ namespace ts::server {
|
|||||||
uint32_t client_total_connections;
|
uint32_t client_total_connections;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CachedPermissionManager {
|
|
||||||
ServerId sid;
|
|
||||||
ClientDbId cldbid;
|
|
||||||
std::weak_ptr<permission::v2::PermissionManager> manager;
|
|
||||||
std::shared_ptr<permission::v2::PermissionManager> ownLock;
|
|
||||||
std::chrono::time_point<std::chrono::system_clock> lastAccess;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CachedProperties {
|
|
||||||
ServerId sid;
|
|
||||||
ClientDbId cldbid;
|
|
||||||
std::weak_ptr<Properties> properties;
|
|
||||||
std::shared_ptr<Properties> ownLock;
|
|
||||||
std::chrono::time_point<std::chrono::system_clock> lastAccess;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
CREATE_TABLE("properties", "`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT", command_append_utf8);
|
|
||||||
CREATE_TABLE("permissions", "`serverId` INT NOT NULL, `type` INT, `id` INT, `channelId` INT, `permId` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` INT, `grant` INT", command_append_utf8);
|
|
||||||
*/
|
|
||||||
struct StartupPermissionEntry {
|
struct StartupPermissionEntry {
|
||||||
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
|
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
|
||||||
uint64_t id = 0;
|
uint64_t id = 0;
|
||||||
@ -64,11 +44,19 @@ namespace ts::server {
|
|||||||
std::string value;
|
std::string value;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StartupCacheEntry {
|
struct DatabaseClient {
|
||||||
ServerId sid;
|
ClientDbId client_database_id;
|
||||||
|
std::string client_unique_id;
|
||||||
|
|
||||||
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions;
|
std::string client_nickname;
|
||||||
std::deque<std::unique_ptr<StartupPropertyEntry>> properties;
|
std::string client_ip;
|
||||||
|
|
||||||
|
std::string client_created; /* seconds since epoch */
|
||||||
|
std::string client_last_connected; /* seconds since epoch */
|
||||||
|
std::string client_total_connections;
|
||||||
|
|
||||||
|
std::string client_login_name;
|
||||||
|
std::string client_description; /* optional and only given sometimes */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FastPropertyEntry {
|
struct FastPropertyEntry {
|
||||||
@ -76,6 +64,8 @@ namespace ts::server {
|
|||||||
std::string value;
|
std::string value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CachedPermissionManager;
|
||||||
|
struct StartupCacheEntry;
|
||||||
class DatabaseHelper {
|
class DatabaseHelper {
|
||||||
public:
|
public:
|
||||||
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
|
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
|
||||||
@ -88,6 +78,15 @@ namespace ts::server {
|
|||||||
size_t cacheBinarySize();
|
size_t cacheBinarySize();
|
||||||
void clearStartupCache(ServerId sid = 0);
|
void clearStartupCache(ServerId sid = 0);
|
||||||
|
|
||||||
|
void handleServerDelete(ServerId /* server id */);
|
||||||
|
|
||||||
|
void listDatabaseClients(
|
||||||
|
ServerId /* server id */,
|
||||||
|
const std::optional<int64_t>& offset,
|
||||||
|
const std::optional<int64_t>& limit,
|
||||||
|
void(* /* callback */)(void* /* user argument */, const DatabaseClient& /* client */),
|
||||||
|
void* /* user argument */);
|
||||||
|
|
||||||
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
|
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
|
||||||
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
|
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
|
||||||
@ -125,9 +124,11 @@ namespace ts::server {
|
|||||||
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
|
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
|
||||||
|
|
||||||
sql::SqlManager* sql = nullptr;
|
sql::SqlManager* sql = nullptr;
|
||||||
threads::Mutex permManagerLock;
|
|
||||||
std::deque<CachedPermissionManager*> cachedPermissionManagers;
|
threads::Mutex cached_permission_manager_lock;
|
||||||
threads::Mutex propsLock;
|
std::deque<std::unique_ptr<CachedPermissionManager>> cached_permission_managers;
|
||||||
std::deque<CachedProperties*> cachedProperties;
|
|
||||||
|
/* Attention: cached_permission_manager_lock should be locked! */
|
||||||
|
[[nodiscard]] inline std::shared_ptr<permission::v2::PermissionManager> find_cached_permission_manager(ServerId /* server id */, ClientDbId /* client id */);
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -163,6 +163,7 @@ struct SnapshotPermissionEntry {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if 0
|
||||||
std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(shared_ptr<VirtualServer> old, std::string host,
|
std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(shared_ptr<VirtualServer> old, std::string host,
|
||||||
uint16_t port, const ts::Command &arguments,
|
uint16_t port, const ts::Command &arguments,
|
||||||
std::string &error) {
|
std::string &error) {
|
||||||
@ -692,6 +693,7 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
|
|||||||
server->ensureValidDefaultGroups();
|
server->ensureValidDefaultGroups();
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
struct CommandTuple {
|
struct CommandTuple {
|
||||||
Command& cmd;
|
Command& cmd;
|
||||||
@ -771,8 +773,8 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
|||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
||||||
if(version == -1) version = 2; //Auto versioned
|
if(version == -1) version = 3; //Auto versioned
|
||||||
if(version < 0 || version > 2) {
|
if(version < 0 || version > 3) {
|
||||||
error = "Invalid snapshot version!";
|
error = "Invalid snapshot version!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -817,44 +819,26 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
|||||||
//Clients
|
//Clients
|
||||||
{
|
{
|
||||||
cmd[index]["begin_clients"] = "";
|
cmd[index]["begin_clients"] = "";
|
||||||
CommandTuple parm{cmd, index, version};
|
|
||||||
auto res = sql::command(server->getSql(), "SELECT `cldbid`, `clientUid`, `firstConnect`, `lastName` FROM `clients` WHERE `serverId` = :sid",
|
|
||||||
variable{":sid", server->getServerId()}
|
|
||||||
).query([&](CommandTuple* commandIndex, int length, char** value, char** name) {
|
|
||||||
ClientDbId cldbid = 0;
|
|
||||||
int64_t clientCreated = 0;
|
|
||||||
string clientUid;
|
|
||||||
string lastName;
|
|
||||||
string description; //TODO description
|
|
||||||
|
|
||||||
for(int idx = 0; idx < length; idx++) {
|
struct CallbackArgument {
|
||||||
try {
|
Command& command;
|
||||||
if(strcmp(name[idx], "cldbid") == 0)
|
int& index;
|
||||||
cldbid = value[idx] && strlen(value[idx]) > 0 ? stoul(value[idx]) : 0;
|
int version;
|
||||||
else if(strcmp(name[idx], "clientUid") == 0)
|
};
|
||||||
clientUid = value[idx];
|
|
||||||
else if(strcmp(name[idx], "firstConnect") == 0)
|
|
||||||
clientCreated = value[idx] && strlen(value[idx]) > 0 ? stoll(value[idx]) : 0L;
|
|
||||||
else if(strcmp(name[idx], "lastName") == 0)
|
|
||||||
lastName = value[idx];
|
|
||||||
} catch (std::exception& ex) {
|
|
||||||
logError(0, "Failed to write snapshot client (Skipping it)! Message: {} @ {} => {}", ex.what(), name[idx], value[idx]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
CallbackArgument callback_argument{cmd, index, version};
|
||||||
|
this->handle->databaseHelper()->listDatabaseClients(server->getServerId(), std::nullopt, std::nullopt, [](void* ptr_argument, const DatabaseClient& client) {
|
||||||
|
auto argument = (CallbackArgument*) ptr_argument;
|
||||||
|
|
||||||
commandIndex->cmd[commandIndex->index]["client_id"] = cldbid;
|
argument->command[argument->index]["client_id"] = client.client_database_id;
|
||||||
commandIndex->cmd[commandIndex->index]["client_unique_id"] = clientUid;
|
argument->command[argument->index]["client_unique_id"] = client.client_unique_id;
|
||||||
commandIndex->cmd[commandIndex->index]["client_nickname"] = lastName;
|
argument->command[argument->index]["client_nickname"] = client.client_nickname;
|
||||||
commandIndex->cmd[commandIndex->index]["client_created"] = clientCreated;
|
argument->command[argument->index]["client_created"] = client.client_created;
|
||||||
commandIndex->cmd[commandIndex->index]["client_description"] = description;
|
argument->command[argument->index]["client_description"] = client.client_description;
|
||||||
if(commandIndex->version == 0)
|
if(argument->version == 0)
|
||||||
commandIndex->cmd[commandIndex->index]["client_unread_messages"] = 0;
|
argument->command[argument->index]["client_unread_messages"] = 0;
|
||||||
commandIndex->index++;
|
argument->index++;
|
||||||
return 0;
|
}, &callback_argument);
|
||||||
}, &parm);
|
|
||||||
LOG_SQL_CMD(res);
|
|
||||||
cmd[index++]["end_clients"] = "";
|
cmd[index++]["end_clients"] = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +110,9 @@ bool VirtualServerManager::initialize(bool autostart) {
|
|||||||
if(id == 0) {
|
if(id == 0) {
|
||||||
logError(LOG_INSTANCE, "Failed to load virtual server from database. Server id is zero!");
|
logError(LOG_INSTANCE, "Failed to load virtual server from database. Server id is zero!");
|
||||||
return 0;
|
return 0;
|
||||||
|
} else if(id == 0xFFFF) {
|
||||||
|
/* snapshot server */
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(host.empty()) {
|
if(host.empty()) {
|
||||||
@ -341,7 +344,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
|
|||||||
if(!sid_success)
|
if(!sid_success)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
this->delete_server_in_db(serverId, true); /* just to ensure */
|
this->delete_server_in_db(serverId, false); /* just to ensure */
|
||||||
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", hosts}, variable{":port", port}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", hosts}, variable{":port", port}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||||
|
|
||||||
auto prop_copy = sql::command(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :target_sid AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `type` = :type AND `id` = 0 AND `serverId` = 0;",
|
auto prop_copy = sql::command(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :target_sid AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `type` = :type AND `id` = 0 AND `serverId` = 0;",
|
||||||
@ -419,7 +422,8 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
|
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
|
||||||
this->delete_server_in_db(server->serverId, true);
|
this->delete_server_in_db(server->serverId, false);
|
||||||
|
this->handle->databaseHelper()->handleServerDelete(server->serverId);
|
||||||
|
|
||||||
if(auto file_vs = file::server()->find_virtual_server(server->getServerId()); file_vs) {
|
if(auto file_vs = file::server()->find_virtual_server(server->getServerId()); file_vs) {
|
||||||
using ErrorType = file::filesystem::ServerCommandErrorType;
|
using ErrorType = file::filesystem::ServerCommandErrorType;
|
||||||
@ -517,7 +521,7 @@ if(!result) { \
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define execute_change(table, column) \
|
#define execute_change(table, column) \
|
||||||
result = sql::command(this->handle->getSql(), "UPDATE TABLE `" table "` SET `" column "` = :nsid WHERE `" column "` = :osid;", \
|
result = sql::command(this->handle->getSql(), "UPDATE `" table "` SET `" column "` = :nsid WHERE `" column "` = :osid;", \
|
||||||
variable{":osid", old_id}, variable{":nsid", new_id}).execute(); \
|
variable{":osid", old_id}, variable{":nsid", new_id}).execute(); \
|
||||||
if(!result) { \
|
if(!result) { \
|
||||||
logWarning(LOG_INSTANCE, "Failed to execute server id change on table {} (column {}): {}", table, column, result.fmtStr()); \
|
logWarning(LOG_INSTANCE, "Failed to execute server id change on table {} (column {}): {}", table, column, result.fmtStr()); \
|
||||||
|
@ -64,12 +64,9 @@ namespace ts::server {
|
|||||||
void executeAutostart();
|
void executeAutostart();
|
||||||
void shutdownAll(const std::string&);
|
void shutdownAll(const std::string&);
|
||||||
|
|
||||||
//Dotn use shared_ptr references to keep sure that they be hold in memory
|
//Don't use shared_ptr references to keep sure that they be hold in memory
|
||||||
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
|
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
|
||||||
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
|
SnapshotDeployResult deploy_snapshot(std::string& /* error */, std::shared_ptr<VirtualServer>& /* target server */, const command_parser& /* source */);
|
||||||
|
|
||||||
//Dotn use shared_ptr references to keep sure that they be hold in memory
|
|
||||||
SnapshotDeployResult deploy_snapshot(std::string& /* error */, std::shared_ptr<VirtualServer> /* target server */, const command_parser& /* source */);
|
|
||||||
|
|
||||||
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
|
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
|
||||||
|
|
||||||
|
@ -529,6 +529,8 @@ int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column)
|
|||||||
|
|
||||||
//assert(type != 0xFF);
|
//assert(type != 0xFF);
|
||||||
assert(channelId != 0);
|
assert(channelId != 0);
|
||||||
|
if(channelId == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
auto channel = std::make_shared<ServerChannel>(parentId, channelId);
|
auto channel = std::make_shared<ServerChannel>(parentId, channelId);
|
||||||
auto server = this->server.lock();
|
auto server = this->server.lock();
|
||||||
|
@ -798,6 +798,30 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
|
|||||||
command_result result;
|
command_result result;
|
||||||
try {
|
try {
|
||||||
result.reset(this->handleCommand(cmd));
|
result.reset(this->handleCommand(cmd));
|
||||||
|
} catch(command_value_cast_failed& ex){
|
||||||
|
auto message = ex.key() + " at " + std::to_string(ex.index()) + " could not be casted to " + ex.target_type().name();
|
||||||
|
if(disconnectOnFail) {
|
||||||
|
this->disconnect(message);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
result.reset(command_result{error::parameter_convert, message});
|
||||||
|
}
|
||||||
|
} catch(command_bulk_index_out_of_bounds_exception& ex){
|
||||||
|
auto message = "missing bulk for index " + std::to_string(ex.index());
|
||||||
|
if(disconnectOnFail) {
|
||||||
|
this->disconnect(message);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
result.reset(command_result{error::parameter_invalid_count, message});
|
||||||
|
}
|
||||||
|
} catch(command_value_missing_exception& ex){
|
||||||
|
auto message = "missing value for " + ex.key() + (ex.bulk_index() == std::string::npos ? "" : " at " + std::to_string(ex.bulk_index()));
|
||||||
|
if(disconnectOnFail) {
|
||||||
|
this->disconnect(message);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
result.reset(command_result{error::parameter_missing, message});
|
||||||
|
}
|
||||||
} catch(invalid_argument& ex){
|
} catch(invalid_argument& ex){
|
||||||
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what());
|
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what());
|
||||||
if(disconnectOnFail) {
|
if(disconnectOnFail) {
|
||||||
|
@ -647,7 +647,7 @@ bool ConnectedClient::handle_text_command(
|
|||||||
//send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id()));
|
//send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id()));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if(TARG(0, "ping")) {
|
} else if(TARG(0, "rtt")) {
|
||||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||||
if(!vc) return false;
|
if(!vc) return false;
|
||||||
|
|
||||||
@ -681,6 +681,16 @@ bool ConnectedClient::handle_text_command(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
} else if(TARG(0, "ping")) {
|
||||||
|
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||||
|
if(!vc) return false;
|
||||||
|
|
||||||
|
auto ping_handler = vc->getConnection()->ping_handler();
|
||||||
|
send_message(this->ref(), "Ping: " + std::to_string(ping_handler.current_ping().count()));
|
||||||
|
|
||||||
|
auto last_response_ms = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::system_clock::now() - ping_handler.last_ping_response()).count();
|
||||||
|
send_message(this->ref(), "Last ping response: " + std::to_string(last_response_ms));
|
||||||
|
return true;
|
||||||
} else if(TARG(0, "disconnect")) {
|
} else if(TARG(0, "disconnect")) {
|
||||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||||
if(!vc) return false;
|
if(!vc) return false;
|
||||||
|
@ -35,7 +35,7 @@ command_result SpeakingClient::handleCommandHandshakeBegin(Command& cmd) { //If
|
|||||||
if(authenticationMethod == IdentityType::TEAMSPEAK) {
|
if(authenticationMethod == IdentityType::TEAMSPEAK) {
|
||||||
this->handshake.identityType = IdentityType::TEAMSPEAK;
|
this->handshake.identityType = IdentityType::TEAMSPEAK;
|
||||||
|
|
||||||
auto identity = base64::decode(cmd["publicKey"]);
|
auto identity = base64::decode(cmd["publicKey"].string());
|
||||||
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(cmd["publicKey"].string()));
|
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(cmd["publicKey"].string()));
|
||||||
|
|
||||||
this->handshake.identityKey = shared_ptr<ecc_key>(new ecc_key{}, free_ecc);
|
this->handshake.identityKey = shared_ptr<ecc_key>(new ecc_key{}, free_ecc);
|
||||||
@ -144,7 +144,7 @@ command_result SpeakingClient::handleCommandHandshakeIdentityProof(Command& cmd)
|
|||||||
this->properties()[property::CLIENT_TEAFORO_NAME] = (*this->handshake.identityData)["user_name"].asString();
|
this->properties()[property::CLIENT_TEAFORO_NAME] = (*this->handshake.identityData)["user_name"].asString();
|
||||||
this->handshake.state = HandshakeState::SUCCEEDED;
|
this->handshake.state = HandshakeState::SUCCEEDED;
|
||||||
} else if(this->handshake.identityType == IdentityType::TEAMSPEAK) {
|
} else if(this->handshake.identityType == IdentityType::TEAMSPEAK) {
|
||||||
auto proof = base64::decode(cmd["proof"]);
|
auto proof = base64::decode(cmd["proof"].string());
|
||||||
|
|
||||||
int result;
|
int result;
|
||||||
if(ecc_verify_hash((u_char*) proof.data(), proof.length(), (u_char*) this->handshake.proof_message.data(), this->handshake.proof_message.length(), &result, this->handshake.identityKey.get()) != CRYPT_OK)
|
if(ecc_verify_hash((u_char*) proof.data(), proof.length(), (u_char*) this->handshake.proof_message.data(), this->handshake.proof_message.length(), &result, this->handshake.identityKey.get()) != CRYPT_OK)
|
||||||
|
@ -367,23 +367,6 @@ command_result ConnectedClient::handleCommandClientChatClosed(Command &cmd) {
|
|||||||
return command_result{error::ok};
|
return command_result{error::ok};
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr static auto kDBListQuery{R"(
|
|
||||||
SELECT `clients`.*, `properties`.`value` as `client_description` FROM (
|
|
||||||
SELECT
|
|
||||||
`clients_server`.`client_database_id`,
|
|
||||||
`clients_server`.`client_unique_id`,
|
|
||||||
`clients_server`.`client_nickname`,
|
|
||||||
`clients_server`.`client_ip`,
|
|
||||||
`clients_server`.`client_created`,
|
|
||||||
`clients_server`.`client_last_connected`,
|
|
||||||
`clients_server`.`client_total_connections`,
|
|
||||||
`clients`.`client_login_name` FROM `clients_server`
|
|
||||||
INNER JOIN `clients` ON `clients`.`client_database_id` = `clients_server`.`client_database_id`
|
|
||||||
WHERE `server_id` = :serverId LIMIT :offset, :limit
|
|
||||||
) AS `clients`
|
|
||||||
LEFT JOIN `properties` ON `properties`.serverId = :serverId AND `properties`.key = 'client_description' AND `properties`.`id` = `clients`.`client_database_id`
|
|
||||||
)"};
|
|
||||||
|
|
||||||
command_result ConnectedClient::handleCommandClientDbList(Command &cmd) {
|
command_result ConnectedClient::handleCommandClientDbList(Command &cmd) {
|
||||||
CMD_REQ_SERVER;
|
CMD_REQ_SERVER;
|
||||||
CMD_RESET_IDLE;
|
CMD_RESET_IDLE;
|
||||||
@ -398,59 +381,32 @@ command_result ConnectedClient::handleCommandClientDbList(Command &cmd) {
|
|||||||
ts::command_builder result{this->notify_response_command("notifyclientdblist")};
|
ts::command_builder result{this->notify_response_command("notifyclientdblist")};
|
||||||
result.reserve_bulks(limit);
|
result.reserve_bulks(limit);
|
||||||
|
|
||||||
size_t command_index{0}, set_index{0};
|
struct CallbackArgument {
|
||||||
|
ts::command_builder& result;
|
||||||
|
bool show_ip{false};
|
||||||
|
size_t command_index{0};
|
||||||
|
};
|
||||||
|
|
||||||
auto show_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
CallbackArgument callback_argument{result};
|
||||||
|
callback_argument.show_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||||
|
serverInstance->databaseHelper()->listDatabaseClients(this->getServerId(), { offset }, { limit }, [](void* ptr_data, const DatabaseClient& client) {
|
||||||
|
auto argument = (CallbackArgument*) ptr_data;
|
||||||
|
|
||||||
auto sqlResult = sql::command{this->server->getSql(), kDBListQuery, variable{":serverId", this->getServerId()}, variable{":offset", offset}, variable{":limit", limit}}
|
auto bulk = argument->result.bulk(argument->command_index++);
|
||||||
.query([&](int length, std::string* values, std::string* names) {
|
|
||||||
set_index++;
|
|
||||||
auto bulk = result.bulk(command_index++);
|
|
||||||
bulk.reserve(300);
|
bulk.reserve(300);
|
||||||
|
|
||||||
auto index{0};
|
bulk.put_unchecked("cldbid", client.client_database_id);
|
||||||
try {
|
bulk.put_unchecked("client_unique_identifier", client.client_unique_id);
|
||||||
assert(names[index] == "client_database_id");
|
bulk.put_unchecked("client_nickname", client.client_nickname);
|
||||||
bulk.put_unchecked("cldbid", values[index++]);
|
bulk.put_unchecked("client_lastip", argument->show_ip ? client.client_ip : "hidden");
|
||||||
|
bulk.put_unchecked("client_lastconnected", client.client_last_connected);
|
||||||
|
bulk.put_unchecked("client_created", client.client_created);
|
||||||
|
bulk.put_unchecked("client_totalconnections", client.client_total_connections);
|
||||||
|
bulk.put_unchecked("client_login_name", client.client_login_name);
|
||||||
|
bulk.put_unchecked("client_description", client.client_description);
|
||||||
|
}, &callback_argument);
|
||||||
|
|
||||||
assert(names[index] == "client_unique_id");
|
if (callback_argument.command_index == 0)
|
||||||
bulk.put_unchecked("client_unique_identifier", values[index++]);
|
|
||||||
|
|
||||||
assert(names[index] == "client_nickname");
|
|
||||||
bulk.put_unchecked("client_nickname", values[index++]);
|
|
||||||
|
|
||||||
assert(names[index] == "client_ip");
|
|
||||||
bulk.put_unchecked("client_lastip", show_ip ? values[index++] : "hidden");
|
|
||||||
|
|
||||||
assert(names[index] == "client_created");
|
|
||||||
bulk.put_unchecked("client_lastconnected", values[index++]);
|
|
||||||
|
|
||||||
assert(names[index] == "client_last_connected");
|
|
||||||
bulk.put_unchecked("client_created", values[index++]);
|
|
||||||
|
|
||||||
assert(names[index] == "client_total_connections");
|
|
||||||
bulk.put_unchecked("client_totalconnections", values[index++]);
|
|
||||||
|
|
||||||
assert(names[index] == "client_login_name");
|
|
||||||
bulk.put_unchecked("client_login_name", values[index++]);
|
|
||||||
|
|
||||||
assert(names[index] == "client_description");
|
|
||||||
bulk.put_unchecked("client_description", values[index++]);
|
|
||||||
|
|
||||||
assert(index == length);
|
|
||||||
} catch (std::exception& ex) {
|
|
||||||
command_index--;
|
|
||||||
logError(this->getServerId(), "Failed to parse client base properties at index {}: {}. Offset: {} Limit: {} Set: {}",
|
|
||||||
index - 1,
|
|
||||||
ex.what(),
|
|
||||||
offset,
|
|
||||||
limit,
|
|
||||||
set_index - 1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (command_index == 0)
|
|
||||||
return command_result{error::database_empty_result};
|
return command_result{error::database_empty_result};
|
||||||
|
|
||||||
if (cmd.hasParm("count")) {
|
if (cmd.hasParm("count")) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "Properties.h"
|
#include "Properties.h"
|
||||||
#include "query/Command.h"
|
#include "query/Command.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <zstd.h>
|
||||||
#include <src/server/QueryServer.h>
|
#include <src/server/QueryServer.h>
|
||||||
#include <src/VirtualServerManager.h>
|
#include <src/VirtualServerManager.h>
|
||||||
#include <src/InstanceHandler.h>
|
#include <src/InstanceHandler.h>
|
||||||
@ -834,6 +835,7 @@ command_result QueryClient::handleCommandBindingList(Command& cmd) {
|
|||||||
|
|
||||||
//TODO with mapping!
|
//TODO with mapping!
|
||||||
command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
|
command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
|
||||||
|
#if 0
|
||||||
CMD_RESET_IDLE;
|
CMD_RESET_IDLE;
|
||||||
|
|
||||||
auto start = system_clock::now();
|
auto start = system_clock::now();
|
||||||
@ -903,20 +905,23 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
|
|||||||
res["time"] = duration_cast<milliseconds>(end - start).count();
|
res["time"] = duration_cast<milliseconds>(end - start).count();
|
||||||
this->sendCommand(res);
|
this->sendCommand(res);
|
||||||
return command_result{error::ok};
|
return command_result{error::ok};
|
||||||
|
#else
|
||||||
|
return command_result{error::command_not_found};
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) {
|
command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) {
|
||||||
CMD_RESET_IDLE;
|
CMD_RESET_IDLE;
|
||||||
|
|
||||||
if(this->server) {
|
if(this->server) {
|
||||||
return command_result{error::not_implemented};
|
|
||||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
||||||
} else {
|
} else {
|
||||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string error{};
|
std::string error{};
|
||||||
auto result = serverInstance->getVoiceServerManager()->deploy_snapshot(error, this->server, command);
|
auto server = this->server;
|
||||||
|
auto result = serverInstance->getVoiceServerManager()->deploy_snapshot(error, server, command);
|
||||||
|
|
||||||
using SnapshotDeployResult = VirtualServerManager::SnapshotDeployResult;
|
using SnapshotDeployResult = VirtualServerManager::SnapshotDeployResult;
|
||||||
switch (result) {
|
switch (result) {
|
||||||
@ -935,6 +940,12 @@ command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::comma
|
|||||||
case SnapshotDeployResult::CUSTOM_ERROR:
|
case SnapshotDeployResult::CUSTOM_ERROR:
|
||||||
return command_result{error::vs_critical, error};
|
return command_result{error::vs_critical, error};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ts::command_builder notify{""};
|
||||||
|
notify.put_unchecked(0, "virtualserver_port", server->properties()[property::VIRTUALSERVER_PORT].value());
|
||||||
|
notify.put_unchecked(0, "sid", server->getServerId());
|
||||||
|
this->sendCommand(notify, false);
|
||||||
|
|
||||||
return command_result{error::ok};
|
return command_result{error::ok};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -943,22 +954,50 @@ command_result QueryClient::handleCommandServerSnapshotCreate(Command& cmd) {
|
|||||||
CMD_RESET_IDLE;
|
CMD_RESET_IDLE;
|
||||||
CMD_REQ_SERVER;
|
CMD_REQ_SERVER;
|
||||||
|
|
||||||
Command result("");
|
Command snapshot_command("");
|
||||||
string error;
|
string error;
|
||||||
|
|
||||||
int version = cmd[0].has("version") ? cmd[0]["version"] : -1;
|
int version = cmd[0].has("version") ? cmd[0]["version"] : -1;
|
||||||
if(version == -1 && (cmd.hasParm("lagacy") || cmd.hasParm("legacy")))
|
if(version == -1 && (cmd.hasParm("lagacy") || cmd.hasParm("legacy"))) {
|
||||||
version = 0;
|
version = 0;
|
||||||
if(!serverInstance->getVoiceServerManager()->createServerSnapshot(result, this->server, version, error))
|
}
|
||||||
return command_result{error::vs_critical, error};
|
|
||||||
|
|
||||||
string data = result.build();
|
if(!serverInstance->getVoiceServerManager()->createServerSnapshot(snapshot_command, this->server, version, error)) {
|
||||||
|
return command_result{error::vs_critical, error};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(version == -1 || version >= 3) {
|
||||||
|
auto build_version = snapshot_command[0]["snapshot_version"].as<int>();
|
||||||
|
snapshot_command.pop_bulk();
|
||||||
|
|
||||||
|
auto snapshot_data = snapshot_command.build();
|
||||||
|
auto max_compressed_size = ZSTD_compressBound(snapshot_data.size());
|
||||||
|
if(ZSTD_isError(max_compressed_size)) {
|
||||||
|
return command_result{error::vs_critical, "failed to calculate compressed size: " + std::string{ZSTD_getErrorName(max_compressed_size)}};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string buffer{};
|
||||||
|
buffer.resize(max_compressed_size);
|
||||||
|
|
||||||
|
auto compressed_size = ZSTD_compress(buffer.data(), buffer.size(), snapshot_data.data(), snapshot_data.length(), 1);
|
||||||
|
if(ZSTD_isError(compressed_size)) {
|
||||||
|
return command_result{error::vs_critical, "failed to compressed snapshot: " + std::string{ZSTD_getErrorName(compressed_size)}};
|
||||||
|
}
|
||||||
|
|
||||||
|
ts::command_builder result{""};
|
||||||
|
result.bulk(0).reserve(100 + compressed_size * 4/3);
|
||||||
|
result.put_unchecked(0, "snapshot_version", build_version);
|
||||||
|
result.put_unchecked(0, "data", base64::encode(std::string_view{buffer.data(), compressed_size}));
|
||||||
|
this->sendCommand(result, false);
|
||||||
|
} else {
|
||||||
|
auto data = snapshot_command.build();
|
||||||
auto buildHash = base64::encode(digest::sha1(data));
|
auto buildHash = base64::encode(digest::sha1(data));
|
||||||
|
|
||||||
result.push_bulk_front();
|
snapshot_command.push_bulk_front();
|
||||||
result[0]["hash"] = buildHash;
|
snapshot_command[0]["hash"] = buildHash;
|
||||||
|
|
||||||
this->sendCommand(result);
|
this->sendCommand(snapshot_command);
|
||||||
|
}
|
||||||
return command_result{error::ok};
|
return command_result{error::ok};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
|
|||||||
|
|
||||||
std::unique_lock state_lock{client->state_lock};
|
std::unique_lock state_lock{client->state_lock};
|
||||||
if(client->connectionState() == ConnectionState::CONNECTED) { /* we've a reconnect */
|
if(client->connectionState() == ConnectionState::CONNECTED) { /* we've a reconnect */
|
||||||
auto lastPingResponse = this->connection->ping_handler().last_ping_response();
|
auto lastPingResponse = std::max(this->connection->ping_handler().last_ping_response(), this->connection->ping_handler().last_command_acknowledged());
|
||||||
if(std::chrono::system_clock::now() - lastPingResponse < std::chrono::seconds(5)) {
|
if(std::chrono::system_clock::now() - lastPingResponse < std::chrono::seconds(5)) {
|
||||||
logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt",
|
logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt",
|
||||||
this->connection->log_prefix(),
|
this->connection->log_prefix(),
|
||||||
@ -162,6 +162,11 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
|
|||||||
this->seed_server = std::string{beta, server_seed_length};
|
this->seed_server = std::string{beta, server_seed_length};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto server_public_key = client->getServer()->publicServerKey();
|
||||||
|
if(server_public_key.empty()) {
|
||||||
|
return ts::command_result{error::vs_critical, "failed to export server public key"};
|
||||||
|
}
|
||||||
|
|
||||||
if(this->new_protocol) {
|
if(this->new_protocol) {
|
||||||
//Pre setup
|
//Pre setup
|
||||||
//Generate chain
|
//Generate chain
|
||||||
@ -193,7 +198,7 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
|
|||||||
answer.put_unchecked(0, "time", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
|
answer.put_unchecked(0, "time", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
|
||||||
answer.put_unchecked(0, "l", base64::encode(crypto_chain));
|
answer.put_unchecked(0, "l", base64::encode(crypto_chain));
|
||||||
answer.put_unchecked(0, "beta", base64::encode(this->seed_server));
|
answer.put_unchecked(0, "beta", base64::encode(this->seed_server));
|
||||||
answer.put_unchecked(0, "omega", client->getServer()->publicServerKey());
|
answer.put_unchecked(0, "omega", server_public_key);
|
||||||
answer.put_unchecked(0, "proof", base64::encode((const char*) sign_buffer, sign_buffer_size));
|
answer.put_unchecked(0, "proof", base64::encode((const char*) sign_buffer, sign_buffer_size));
|
||||||
answer.put_unchecked(0, "tvd", "");
|
answer.put_unchecked(0, "tvd", "");
|
||||||
answer.put_unchecked(0, "root", base64::encode((char*) this->chain_data->public_key, 32));
|
answer.put_unchecked(0, "root", base64::encode((char*) this->chain_data->public_key, 32));
|
||||||
@ -206,11 +211,6 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
|
|||||||
} else {
|
} else {
|
||||||
debugMessage(this->connection->virtual_server_id(), "{} Got non client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_, this->client_protocol_time_);
|
debugMessage(this->connection->virtual_server_id(), "{} Got non client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_, this->client_protocol_time_);
|
||||||
|
|
||||||
auto server_public_key = client->getServer()->publicServerKey();
|
|
||||||
if(server_public_key.empty()) {
|
|
||||||
return ts::command_result{error::vs_critical, "failed to export server public key"};
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
ts::command_builder answer{"initivexpand"};
|
ts::command_builder answer{"initivexpand"};
|
||||||
answer.put_unchecked(0, "alpha", base64::encode(this->seed_client));
|
answer.put_unchecked(0, "alpha", base64::encode(this->seed_client));
|
||||||
|
@ -4,12 +4,9 @@
|
|||||||
#include "PacketEncoder.h"
|
#include "PacketEncoder.h"
|
||||||
|
|
||||||
#include <protocol/buffers.h>
|
#include <protocol/buffers.h>
|
||||||
#include <protocol/AcknowledgeManager.h>
|
|
||||||
#include <protocol/CompressionHandler.h>
|
#include <protocol/CompressionHandler.h>
|
||||||
#include <protocol/CryptHandler.cpp>
|
#include <protocol/CryptHandler.cpp>
|
||||||
|
|
||||||
#include "../../ConnectionStatistics.h"
|
|
||||||
|
|
||||||
using namespace ts;
|
using namespace ts;
|
||||||
using namespace ts::server::server::udp;
|
using namespace ts::server::server::udp;
|
||||||
|
|
||||||
@ -164,9 +161,9 @@ void PacketEncoder::send_command(const std::string_view &command, bool low, std:
|
|||||||
packets_tail = &packet->next;
|
packets_tail = &packet->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard id_lock{this->packet_id_mutex};
|
std::lock_guard id_lock{this->packet_id_mutex};
|
||||||
|
{
|
||||||
uint32_t full_id;
|
uint32_t full_id;
|
||||||
auto head = packets_head;
|
auto head = packets_head;
|
||||||
while(head) {
|
while(head) {
|
||||||
@ -174,24 +171,30 @@ void PacketEncoder::send_command(const std::string_view &command, bool low, std:
|
|||||||
|
|
||||||
head->set_packet_id(full_id & 0xFFFFU);
|
head->set_packet_id(full_id & 0xFFFFU);
|
||||||
head->generation = full_id >> 16U;
|
head->generation = full_id >> 16U;
|
||||||
|
|
||||||
|
/* loss stats (In order required so we're using the this->packet_id_mutex) */
|
||||||
|
this->packet_statistics_->send_command(head->packet_type(), full_id);
|
||||||
|
|
||||||
head = head->next;
|
head = head->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
packets_head->type_and_flags |= head_pflags;
|
packets_head->type_and_flags |= head_pflags;
|
||||||
|
|
||||||
/* general stats */
|
/* general stats */
|
||||||
|
{
|
||||||
auto head = packets_head;
|
auto head = packets_head;
|
||||||
while(head) {
|
while(head) {
|
||||||
this->callback_connection_stats(this->callback_data, StatisticsCategory::COMMAND, head->packet_length() + 96); /* 96 for the UDP overhead */
|
this->callback_connection_stats(this->callback_data, StatisticsCategory::COMMAND, head->packet_length() + 96); /* 96 for the UDP overhead */
|
||||||
head = head->next;
|
head = head->next;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* loss stats */
|
/* ack handler */
|
||||||
{
|
{
|
||||||
auto head = packets_head;
|
auto head = packets_head;
|
||||||
while(head) {
|
while(head) {
|
||||||
auto full_packet_id = (uint32_t) (head->generation << 16U) | head->packet_id();
|
auto full_packet_id = (uint32_t) (head->generation << 16U) | head->packet_id();
|
||||||
this->packet_statistics_->send_command(head->packet_type(), full_packet_id);
|
|
||||||
|
|
||||||
/* increase a reference for the ack handler */
|
/* increase a reference for the ack handler */
|
||||||
head->ref();
|
head->ref();
|
||||||
@ -230,9 +233,18 @@ void PacketEncoder::encrypt_pending_packets() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
auto packet = packets_head;
|
auto packet = packets_head;
|
||||||
|
auto packet_tail = packet;
|
||||||
while(packet) {
|
while(packet) {
|
||||||
this->prepare_outgoing_packet(packet);
|
this->prepare_outgoing_packet(packet);
|
||||||
packet = packet->next;
|
packet = packet->next;
|
||||||
|
if(packet)
|
||||||
|
packet_tail = packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard wlock{this->write_queue_mutex};
|
||||||
|
*this->resend_queue_tail = packets_head;
|
||||||
|
this->resend_queue_tail = &packet_tail->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +279,7 @@ bool PacketEncoder::prepare_outgoing_packet(ts::protocol::OutgoingServerPacket *
|
|||||||
}
|
}
|
||||||
|
|
||||||
PacketEncoder::BufferPopResult PacketEncoder::pop_write_buffer(protocol::OutgoingServerPacket *&result) {
|
PacketEncoder::BufferPopResult PacketEncoder::pop_write_buffer(protocol::OutgoingServerPacket *&result) {
|
||||||
bool need_prepare_packet{false}, more_packets{false};
|
bool need_prepare_packet{false}, more_packets;
|
||||||
{
|
{
|
||||||
std::lock_guard wlock{this->write_queue_mutex};
|
std::lock_guard wlock{this->write_queue_mutex};
|
||||||
if(this->resend_queue_head) {
|
if(this->resend_queue_head) {
|
||||||
|
@ -18,13 +18,14 @@ namespace ts::server::server::udp {
|
|||||||
|
|
||||||
[[nodiscard]] inline std::chrono::milliseconds current_ping() const { return this->current_ping_; }
|
[[nodiscard]] inline std::chrono::milliseconds current_ping() const { return this->current_ping_; }
|
||||||
[[nodiscard]] inline std::chrono::system_clock::time_point last_ping_response() const { return this->last_response_; }
|
[[nodiscard]] inline std::chrono::system_clock::time_point last_ping_response() const { return this->last_response_; }
|
||||||
|
[[nodiscard]] inline std::chrono::system_clock::time_point last_command_acknowledged() const { return this->last_command_acknowledge_; }
|
||||||
|
|
||||||
void* callback_argument{nullptr};
|
void* callback_argument{nullptr};
|
||||||
callback_send_ping_t callback_send_ping{nullptr};
|
callback_send_ping_t callback_send_ping{nullptr};
|
||||||
callback_send_recovery_command_t callback_send_recovery_command{nullptr};
|
callback_send_recovery_command_t callback_send_recovery_command{nullptr};
|
||||||
callback_time_outed_t callback_time_outed{nullptr};
|
callback_time_outed_t callback_time_outed{nullptr};
|
||||||
private:
|
private:
|
||||||
constexpr static std::chrono::milliseconds kPingRequestInterval{2500};
|
constexpr static std::chrono::milliseconds kPingRequestInterval{1000};
|
||||||
constexpr static std::chrono::milliseconds kPingTimeout{15 * 1000};
|
constexpr static std::chrono::milliseconds kPingTimeout{15 * 1000};
|
||||||
|
|
||||||
constexpr static std::chrono::milliseconds kRecoveryRequestInterval{1000};
|
constexpr static std::chrono::milliseconds kRecoveryRequestInterval{1000};
|
||||||
|
@ -170,11 +170,18 @@ bool SqlDataManager::initialize(std::string& error) {
|
|||||||
} else {
|
} else {
|
||||||
res = sql::command(this->sql(), "UPDATE `general` SET `value`= :value WHERE `key`= :key;", variable{":key", "lock_test"}, variable{":value", "TeaSpeak created by WolverinDEV <3"}).execute();
|
res = sql::command(this->sql(), "UPDATE `general` SET `value`= :value WHERE `key`= :key;", variable{":key", "lock_test"}, variable{":value", "TeaSpeak created by WolverinDEV <3"}).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!res) {
|
if(!res) {
|
||||||
if(res.msg().find("database is locked") != string::npos) error = "database is locked";
|
if(res.msg().find("database is locked") != string::npos) error = "database is locked";
|
||||||
else error = "Failed to execute lock test! Command result: " + res.fmtStr();
|
else error = "Failed to execute lock test! Command result: " + res.fmtStr();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res = sql::command{this->sql(), "DELETE FROM `general` WHERE `key` = :key ORDER BY `key` LIMIT -1 OFFSET 1;", variable{":key", "lock_test"}}.execute();
|
||||||
|
if(!res) {
|
||||||
|
error = res.fmtStr();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this->update_permissions(error)) {
|
if(!this->update_permissions(error)) {
|
||||||
@ -500,16 +507,17 @@ bool SqlDataManager::update_database(std::string &error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 13: {
|
case 13: {
|
||||||
constexpr static std::array<std::string_view, 7> kUpdateCommands{
|
constexpr static std::array<std::string_view, 8> kUpdateCommands{
|
||||||
"CREATE INDEX `idx_properties_serverid_id_value` ON `properties` (`serverId`, `id`, `key`);",
|
"CREATE INDEX `idx_properties_serverid_id_value` ON `properties` (`serverId`, `id`, `key`);",
|
||||||
|
"CREATE INDEX `idx_properties_serverid_id_type` ON `properties` (`serverId`, `id`, `type`);",
|
||||||
|
|
||||||
"CREATE TABLE `groups_v2` (`serverId` INT NOT NULL, `groupId` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, `target` INT, `type` INT, `displayName` VARCHAR(128), `original_group_id` INTEGER);",
|
"CREATE TABLE `groups_v2` (`serverId` INT NOT NULL, `groupId` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `target` INT, `type` INT, `displayName` VARCHAR(128), `original_group_id` INTEGER);",
|
||||||
"INSERT INTO `groups_v2` (`serverId`, `groupId`, `target`, `type`, `displayName`) SELECT `serverId`, `groupId`, `target`, `type`, `displayName` FROM `groups`;",
|
"INSERT INTO `groups_v2` (`serverId`, `groupId`, `target`, `type`, `displayName`) SELECT `serverId`, `groupId`, `target`, `type`, `displayName` FROM `groups`;",
|
||||||
"DROP TABLE `groups`;",
|
"DROP TABLE `groups`;",
|
||||||
"ALTER TABLE `groups_v2` RENAME TO groups;",
|
"ALTER TABLE `groups_v2` RENAME TO groups;",
|
||||||
|
|
||||||
"CREATE INDEX `idx_groups_serverId_groupId` ON `groups` (`server_id`);",
|
"CREATE INDEX `idx_groups_serverId_groupId` ON `groups` (`serverId`);",
|
||||||
"CREATE INDEX `idx_groups_serverid` ON `groups` (`server_id`, `groupId`);"
|
"CREATE INDEX `idx_groups_serverid` ON `groups` (`serverId`, `groupId`);"
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!execute_commands(this->sql(), error, kUpdateCommands))
|
if(!execute_commands(this->sql(), error, kUpdateCommands))
|
||||||
|
@ -30,7 +30,7 @@ bool TokenManager::loadTokens() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int TokenManager::loadTokenFromDb(int length, char **values, char **columns) {
|
int TokenManager::loadTokenFromDb(int length, char **values, char **columns) {
|
||||||
std::string token, description = "";
|
std::string token, description;
|
||||||
auto type = static_cast<TokenType>(0xFF);
|
auto type = static_cast<TokenType>(0xFF);
|
||||||
GroupId group = 0;
|
GroupId group = 0;
|
||||||
ChannelId chId = 0;
|
ChannelId chId = 0;
|
||||||
|
@ -16,17 +16,15 @@
|
|||||||
|
|
||||||
using namespace ts;
|
using namespace ts;
|
||||||
using namespace ts::server;
|
using namespace ts::server;
|
||||||
using SnapshotType = ts::server::snapshots::type;
|
|
||||||
using SnapshotVersion = ts::server::snapshots::version_t;
|
|
||||||
|
|
||||||
constexpr static ServerId kSnapshotServerId{0xFFFF};
|
constexpr static ServerId kSnapshotServerId{0xFFFF};
|
||||||
VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot(std::string &error, std::shared_ptr<VirtualServer> server, const command_parser &command) {
|
VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot(std::string &error, std::shared_ptr<VirtualServer>& server, const command_parser &command) {
|
||||||
if(!server) {
|
if(!server) {
|
||||||
auto instances = this->serverInstances();
|
auto instance_count = this->serverInstances().size();
|
||||||
if(config::server::max_virtual_server != -1 && instances.size() > config::server::max_virtual_server)
|
if(config::server::max_virtual_server != -1 && instance_count > config::server::max_virtual_server)
|
||||||
return SnapshotDeployResult::REACHED_CONFIG_SERVER_LIMIT;
|
return SnapshotDeployResult::REACHED_CONFIG_SERVER_LIMIT;
|
||||||
|
|
||||||
if(instances.size() >= 65534)
|
if(instance_count >= 65534)
|
||||||
return SnapshotDeployResult::REACHED_SOFTWARE_SERVER_LIMIT;
|
return SnapshotDeployResult::REACHED_SOFTWARE_SERVER_LIMIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +34,7 @@ VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot
|
|||||||
return SnapshotDeployResult::REACHED_SERVER_ID_LIMIT;
|
return SnapshotDeployResult::REACHED_SERVER_ID_LIMIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->delete_server_in_db(kSnapshotServerId, true);
|
this->delete_server_in_db(kSnapshotServerId, false);
|
||||||
auto result = sql::command{this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)"}
|
auto result = sql::command{this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)"}
|
||||||
.value(":sid", kSnapshotServerId)
|
.value(":sid", kSnapshotServerId)
|
||||||
.value(":host", server ? server->properties()[property::VIRTUALSERVER_HOST].value() : config::binding::DefaultVoiceHost)
|
.value(":host", server ? server->properties()[property::VIRTUALSERVER_HOST].value() : config::binding::DefaultVoiceHost)
|
||||||
@ -53,7 +51,7 @@ VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!server) {
|
if(!server) {
|
||||||
this->delete_server_in_db(target_server_id, true);
|
this->delete_server_in_db(target_server_id, false);
|
||||||
this->change_server_id_in_db(kSnapshotServerId, target_server_id);
|
this->change_server_id_in_db(kSnapshotServerId, target_server_id);
|
||||||
} else {
|
} else {
|
||||||
this->deleteServer(server);
|
this->deleteServer(server);
|
||||||
@ -66,6 +64,7 @@ VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot
|
|||||||
//FIXME error handling
|
//FIXME error handling
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
|
||||||
{
|
{
|
||||||
threads::MutexLock l(this->instanceLock);
|
threads::MutexLock l(this->instanceLock);
|
||||||
this->instances.push_back(server);
|
this->instances.push_back(server);
|
||||||
@ -74,59 +73,15 @@ VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot
|
|||||||
return SnapshotDeployResult::SUCCESS;
|
return SnapshotDeployResult::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
bool VirtualServerManager::deploy_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &command) {
|
|
||||||
auto result = this->try_deploy_snapshot(error, server_id, command);
|
|
||||||
if(!result) this->delete_server_in_db(server_id, true);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId target_server_id, ts::ServerId logging_server_id, const ts::command_parser &command) {
|
bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId target_server_id, ts::ServerId logging_server_id, const ts::command_parser &command) {
|
||||||
snapshots::snapshot_data snapshot_data{};
|
snapshots::snapshot_data snapshot_data{};
|
||||||
|
|
||||||
if(!snapshots::parse_snapshot(snapshot_data, error, logging_server_id, command))
|
if(!snapshots::parse_snapshot(snapshot_data, error, logging_server_id, command))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::map<ClientDbId, ClientDbId> client_id_mapping{};
|
snapshots::snapshot_mappings mappings{};
|
||||||
std::map<ChannelId, ChannelId> channel_id_mapping{};
|
|
||||||
std::map<ChannelId, ChannelId> channel_group_id_mapping{};
|
|
||||||
std::map<ChannelId, ChannelId> server_group_id_mapping{};
|
|
||||||
std::map<PlaylistId, PlaylistId> playlist_id_mapping{};
|
|
||||||
|
|
||||||
/* lets start inserting data to the database */
|
/* register clients */
|
||||||
{
|
|
||||||
/* register server & properties */
|
|
||||||
{
|
|
||||||
|
|
||||||
sql::InsertQuery insert_property_query{"properties",
|
|
||||||
sql::Column<ServerId>("serverId"),
|
|
||||||
sql::Column<property::PropertyType>("type"),
|
|
||||||
sql::Column<uint64_t>("id"),
|
|
||||||
sql::Column<std::string>("key"),
|
|
||||||
sql::Column<std::string>("value")
|
|
||||||
};
|
|
||||||
|
|
||||||
for(const auto& property : snapshot_data.parsed_server.properties.list_properties(property::FLAG_SAVE)) {
|
|
||||||
if(!property.isModified()) continue;
|
|
||||||
|
|
||||||
insert_property_query.add_entry(
|
|
||||||
target_server_id,
|
|
||||||
property::PROP_TYPE_SERVER,
|
|
||||||
0,
|
|
||||||
std::string{property.type().name},
|
|
||||||
property.value()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto result = insert_property_query.execute(this->handle->getSql(), true);
|
|
||||||
if(!result.failed_entries.empty())
|
|
||||||
logWarning(logging_server_id, "Failed to insert all server properties into the database. Failed property count: {}", result.failed_entries.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* register clients and bots */
|
|
||||||
{
|
{
|
||||||
sql::InsertQuery insert_general_query{"clients",
|
sql::InsertQuery insert_general_query{"clients",
|
||||||
sql::Column<std::string>("client_unique_id"),
|
sql::Column<std::string>("client_unique_id"),
|
||||||
@ -140,12 +95,6 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
std::chrono::floor<std::chrono::seconds>(client.timestamp_created.time_since_epoch()).count()
|
std::chrono::floor<std::chrono::seconds>(client.timestamp_created.time_since_epoch()).count()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for(const auto& client : snapshot_data.music_bots) {
|
|
||||||
insert_general_query.add_entry(
|
|
||||||
client.unique_id,
|
|
||||||
current_time_seconds
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = insert_general_query.execute(this->handle->getSql(), true);
|
auto result = insert_general_query.execute(this->handle->getSql(), true);
|
||||||
for(const auto& fail : result.failed_entries)
|
for(const auto& fail : result.failed_entries)
|
||||||
@ -179,10 +128,10 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
logWarning(logging_server_id, "Failed to insert music bot {} into the database: {}", client.unique_id, sqlResult.fmtStr());
|
logWarning(logging_server_id, "Failed to insert music bot {} into the database: {}", client.unique_id, sqlResult.fmtStr());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto map_query_result = sql::command{this->handle->getSql(), "SELECT `original_client_id`,`client_database_id` FROM `clients_server` WHERE `server_id` = :sid;"}
|
auto map_query_result = sql::command{this->handle->getSql(), "SELECT `original_client_id`,`client_database_id` FROM `clients_server` WHERE `server_id` = :serverId;"}
|
||||||
.value(":serverId", target_server_id)
|
.value(":serverId", target_server_id)
|
||||||
.query([&](int length, std::string* values, std::string* names) {
|
.query([&](int length, std::string* values, std::string* names) {
|
||||||
ClientDbId original_id{0}, new_id{0};
|
ClientDbId original_id, new_id;
|
||||||
try {
|
try {
|
||||||
original_id = std::stoull(values[0]);
|
original_id = std::stoull(values[0]);
|
||||||
new_id = std::stoull(values[1]);
|
new_id = std::stoull(values[1]);
|
||||||
@ -190,7 +139,7 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
logWarning(logging_server_id, "Failed to parse client database entry mapping for group id {} (New ID: {})", values[1], values[0]);
|
logWarning(logging_server_id, "Failed to parse client database entry mapping for group id {} (New ID: {})", values[1], values[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client_id_mapping[original_id] = new_id;
|
mappings.client_id[original_id] = new_id;
|
||||||
});
|
});
|
||||||
if(!map_query_result) {
|
if(!map_query_result) {
|
||||||
error = "failed to query client database id mappings (" + map_query_result.fmtStr() + ")";
|
error = "failed to query client database id mappings (" + map_query_result.fmtStr() + ")";
|
||||||
@ -201,10 +150,10 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
/* channels */
|
/* channels */
|
||||||
{
|
{
|
||||||
/* Assign each channel a new id */
|
/* Assign each channel a new id */
|
||||||
ChannelId current_id{0};
|
ChannelId current_id{1};
|
||||||
for(auto& channel : snapshot_data.parsed_channels) {
|
for(auto& channel : snapshot_data.parsed_channels) {
|
||||||
const auto new_id = current_id++;
|
const auto new_id = current_id++;
|
||||||
channel_id_mapping[channel.properties[property::CHANNEL_ID]] = new_id;
|
mappings.channel_id[channel.properties[property::CHANNEL_ID]] = new_id;
|
||||||
channel.properties[property::CHANNEL_ID] = new_id;
|
channel.properties[property::CHANNEL_ID] = new_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,8 +161,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
for(auto& channel : snapshot_data.parsed_channels) {
|
for(auto& channel : snapshot_data.parsed_channels) {
|
||||||
auto pid = channel.properties[property::CHANNEL_PID].as<ChannelId>();
|
auto pid = channel.properties[property::CHANNEL_PID].as<ChannelId>();
|
||||||
if(pid > 0) {
|
if(pid > 0) {
|
||||||
auto new_id = channel_id_mapping.find(pid);
|
auto new_id = mappings.channel_id.find(pid);
|
||||||
if(new_id == channel_id_mapping.end()) {
|
if(new_id == mappings.channel_id.end()) {
|
||||||
error = "failed to remap channel parent id for channel \"" + channel.properties[property::CHANNEL_NAME].value() + "\" (snapshot/channel tree broken?)";
|
error = "failed to remap channel parent id for channel \"" + channel.properties[property::CHANNEL_NAME].value() + "\" (snapshot/channel tree broken?)";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -287,8 +236,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
|
|
||||||
for(auto& entry : snapshot_data.channel_permissions) {
|
for(auto& entry : snapshot_data.channel_permissions) {
|
||||||
{
|
{
|
||||||
auto new_id = channel_id_mapping.find(entry.id1);
|
auto new_id = mappings.channel_id.find(entry.id1);
|
||||||
if(new_id == channel_id_mapping.end()) {
|
if(new_id == mappings.channel_id.end()) {
|
||||||
logWarning(logging_server_id, "Missing channel id mapping for channel permission entry (channel id: {}). Skipping permission insert.", entry.id1);
|
logWarning(logging_server_id, "Missing channel id mapping for channel permission entry (channel id: {}). Skipping permission insert.", entry.id1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -299,8 +248,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
insert_query.add_entry(
|
insert_query.add_entry(
|
||||||
target_server_id,
|
target_server_id,
|
||||||
permission::SQL_PERM_CHANNEL,
|
permission::SQL_PERM_CHANNEL,
|
||||||
entry.id1,
|
|
||||||
0,
|
0,
|
||||||
|
entry.id1,
|
||||||
permission.type->name,
|
permission.type->name,
|
||||||
permission.value.has_value ? permission.value.value : permNotGranted,
|
permission.value.has_value ? permission.value.value : permNotGranted,
|
||||||
permission.granted.has_value ? permission.granted.value : permNotGranted,
|
permission.granted.has_value ? permission.granted.value : permNotGranted,
|
||||||
@ -323,13 +272,13 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
for(auto& group : snapshot_data.parsed_server_groups) {
|
for(auto& group : snapshot_data.parsed_server_groups) {
|
||||||
auto result = insert_model.command().value(":name", group.name).value(":id", group.group_id).execute();
|
auto result = insert_model.command().value(":name", group.name).value(":id", group.group_id).execute();
|
||||||
if(!result)
|
if(!result)
|
||||||
logWarning(logging_server_id, "Failed to insert server group \"{}\" into the database", group.name);
|
logWarning(logging_server_id, "Failed to insert server group \"{}\" into the database: {}", group.name, result.fmtStr());
|
||||||
}
|
}
|
||||||
|
|
||||||
sql::command{this->handle->getSql(), "SELECT `original_group_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
|
sql::command{this->handle->getSql(), "SELECT `original_group_id`,`groupId` FROM `groups` WHERE `serverId` = :serverId AND `target` = :target AND `type` = :type"}
|
||||||
.value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL)
|
.value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL)
|
||||||
.query([&](int length, std::string* values, std::string* names) {
|
.query([&](int length, std::string* values, std::string* names) {
|
||||||
GroupId original_id{0}, new_id{0};
|
GroupId original_id, new_id;
|
||||||
try {
|
try {
|
||||||
original_id = std::stoull(values[0]);
|
original_id = std::stoull(values[0]);
|
||||||
new_id = std::stoull(values[1]);
|
new_id = std::stoull(values[1]);
|
||||||
@ -337,7 +286,7 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
logWarning(logging_server_id, "Failed to parse server group mapping for group id {} (New ID: {})", values[1], values[0]);
|
logWarning(logging_server_id, "Failed to parse server group mapping for group id {} (New ID: {})", values[1], values[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
server_group_id_mapping[original_id] = new_id;
|
mappings.server_group_id[original_id] = new_id;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,18 +303,28 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
for(auto& entry : relation.second) {
|
for(auto& entry : relation.second) {
|
||||||
ClientId client_id{};
|
ClientId client_id{};
|
||||||
{
|
{
|
||||||
auto new_id = client_id_mapping.find(entry.client_id);
|
auto new_id = mappings.client_id.find(entry.client_id);
|
||||||
if(new_id == client_id_mapping.end()) {
|
if(new_id == mappings.client_id.end()) {
|
||||||
logWarning(logging_server_id, "Missing client id mapping for channel group relation permission entry (client id: {}). Skipping relation insert.", entry.client_id);
|
logWarning(logging_server_id, "Missing client id mapping for channel group relation permission entry (client id: {}). Skipping relation insert.", entry.client_id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
client_id = new_id->second;
|
client_id = new_id->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GroupId group_id;
|
||||||
|
{
|
||||||
|
auto new_id = mappings.server_group_id.find(entry.group_id);
|
||||||
|
if(new_id == mappings.server_group_id.end()) {
|
||||||
|
logWarning(logging_server_id, "Missing server group mapping of group {} for client {}. Skipping relation insert.", entry.group_id, entry.client_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
group_id = new_id->second;
|
||||||
|
}
|
||||||
|
|
||||||
insert_query.add_entry(
|
insert_query.add_entry(
|
||||||
target_server_id,
|
target_server_id,
|
||||||
client_id,
|
client_id,
|
||||||
entry.group_id,
|
group_id,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -376,21 +335,67 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
logWarning(logging_server_id, "Failed to insert all server group relations into the database. Failed insert count: {}", result.failed_entries.size());
|
logWarning(logging_server_id, "Failed to insert all server group relations into the database. Failed insert count: {}", result.failed_entries.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* server group permissions */
|
||||||
|
{
|
||||||
|
sql::InsertQuery insert_query{"permissions",
|
||||||
|
sql::Column<ServerId>("serverId"),
|
||||||
|
sql::Column<permission::PermissionSqlType>("type"),
|
||||||
|
sql::Column<uint64_t>("id"),
|
||||||
|
sql::Column<ChannelId>("channelId"),
|
||||||
|
sql::Column<std::string>("permId"),
|
||||||
|
|
||||||
|
sql::Column<permission::PermissionValue>("value"),
|
||||||
|
sql::Column<permission::PermissionValue>("grant"),
|
||||||
|
sql::Column<bool>("flag_skip"),
|
||||||
|
sql::Column<bool>("flag_negate"),
|
||||||
|
};
|
||||||
|
|
||||||
|
for(auto& group : snapshot_data.parsed_server_groups) {
|
||||||
|
GroupId group_id;
|
||||||
|
{
|
||||||
|
auto new_id = mappings.server_group_id.find(group.group_id);
|
||||||
|
if(new_id == mappings.server_group_id.end()) {
|
||||||
|
logWarning(logging_server_id, "Missing server group mapping of group {}. Skipping permission insert.", group.group_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
group_id = new_id->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& permission : group.permissions) {
|
||||||
|
insert_query.add_entry(
|
||||||
|
target_server_id,
|
||||||
|
permission::SQL_PERM_GROUP,
|
||||||
|
group_id,
|
||||||
|
0,
|
||||||
|
permission.type->name,
|
||||||
|
permission.value.has_value ? permission.value.value : permNotGranted,
|
||||||
|
permission.granted.has_value ? permission.granted.value : permNotGranted,
|
||||||
|
permission.flag_skip,
|
||||||
|
permission.flag_negate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = insert_query.execute(this->handle->getSql(), true);
|
||||||
|
if(!result.failed_entries.empty())
|
||||||
|
logWarning(logging_server_id, "Failed to insert some channel group permissions into the database. Failed permission count: {}", result.failed_entries.size());
|
||||||
|
}
|
||||||
|
|
||||||
/* channel groups */
|
/* channel groups */
|
||||||
{
|
{
|
||||||
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_group_id`) VALUES (:serverId, :target, :type, :name, :id)"};
|
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_group_id`) VALUES (:serverId, :target, :type, :name, :id)"};
|
||||||
insert_model.value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL);
|
insert_model.value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL);
|
||||||
|
|
||||||
for(auto& group : snapshot_data.parsed_server_groups) {
|
for(auto& group : snapshot_data.parsed_channel_groups) {
|
||||||
auto result = insert_model.command().value(":name", group.name).value(":id", group.group_id).execute();
|
auto result = insert_model.command().value(":name", group.name).value(":id", group.group_id).execute();
|
||||||
if(!result)
|
if(!result)
|
||||||
logWarning(logging_server_id, "Failed to insert channel group \"{}\" into the database", group.name);
|
logWarning(logging_server_id, "Failed to insert channel group \"{}\" into the database: {}", group.name, result.fmtStr());
|
||||||
}
|
}
|
||||||
|
|
||||||
sql::command{this->handle->getSql(), "SELECT `original_group_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
|
sql::command{this->handle->getSql(), "SELECT `original_group_id`,`groupId` FROM `groups` WHERE `serverId` = :serverId AND `target` = :target AND `type` = :type"}
|
||||||
.value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL)
|
.value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL)
|
||||||
.query([&](int length, std::string* values, std::string* names) {
|
.query([&](int length, std::string* values, std::string* names) {
|
||||||
GroupId original_id{0}, new_id{0};
|
GroupId original_id, new_id;
|
||||||
try {
|
try {
|
||||||
original_id = std::stoull(values[0]);
|
original_id = std::stoull(values[0]);
|
||||||
new_id = std::stoull(values[1]);
|
new_id = std::stoull(values[1]);
|
||||||
@ -398,7 +403,7 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
logWarning(logging_server_id, "Failed to parse channel group mapping for group id {} (New ID: {})", values[1], values[0]);
|
logWarning(logging_server_id, "Failed to parse channel group mapping for group id {} (New ID: {})", values[1], values[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
channel_group_id_mapping[original_id] = new_id;
|
mappings.channel_group_id[original_id] = new_id;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,10 +417,10 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
};
|
};
|
||||||
|
|
||||||
for(auto& relation : snapshot_data.parsed_channel_group_relations) {
|
for(auto& relation : snapshot_data.parsed_channel_group_relations) {
|
||||||
ChannelId channel_id{};
|
ChannelId channel_id;
|
||||||
{
|
{
|
||||||
auto new_id = channel_id_mapping.find(relation.first);
|
auto new_id = mappings.channel_id.find(relation.first);
|
||||||
if(new_id == channel_id_mapping.end()) {
|
if(new_id == mappings.channel_id.end()) {
|
||||||
logWarning(logging_server_id, "Missing channel id mapping for channel group relation entry (channel id: {}). Skipping relation insert.", relation.first);
|
logWarning(logging_server_id, "Missing channel id mapping for channel group relation entry (channel id: {}). Skipping relation insert.", relation.first);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -423,20 +428,30 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(auto& entry : relation.second) {
|
for(auto& entry : relation.second) {
|
||||||
ClientId client_id{};
|
ClientId client_id;
|
||||||
{
|
{
|
||||||
auto new_id = client_id_mapping.find(entry.client_id);
|
auto new_id = mappings.client_id.find(entry.client_id);
|
||||||
if(new_id == client_id_mapping.end()) {
|
if(new_id == mappings.client_id.end()) {
|
||||||
logWarning(logging_server_id, "Missing client id mapping for channel group relation permission entry (client id: {}, channel id: {}). Skipping relation insert.", entry.client_id, relation.first);
|
logWarning(logging_server_id, "Missing client id mapping for channel group relation permission entry (client id: {}, channel id: {}). Skipping relation insert.", entry.client_id, relation.first);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
client_id = new_id->second;
|
client_id = new_id->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GroupId group_id;
|
||||||
|
{
|
||||||
|
auto new_id = mappings.channel_group_id.find(entry.group_id);
|
||||||
|
if(new_id == mappings.channel_group_id.end()) {
|
||||||
|
logWarning(logging_server_id, "Missing channel group mapping of group {} for client {} for channel {}. Skipping relation insert.", entry.group_id, entry.client_id, channel_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
group_id = new_id->second;
|
||||||
|
}
|
||||||
|
|
||||||
insert_query.add_entry(
|
insert_query.add_entry(
|
||||||
target_server_id,
|
target_server_id,
|
||||||
client_id,
|
client_id,
|
||||||
entry.group_id,
|
group_id,
|
||||||
channel_id
|
channel_id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -447,6 +462,111 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
logWarning(logging_server_id, "Failed to insert all channel group relations into the database. Failed insert count: {}", result.failed_entries.size());
|
logWarning(logging_server_id, "Failed to insert all channel group relations into the database. Failed insert count: {}", result.failed_entries.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* channel group permissions */
|
||||||
|
{
|
||||||
|
sql::InsertQuery insert_query{"permissions",
|
||||||
|
sql::Column<ServerId>("serverId"),
|
||||||
|
sql::Column<permission::PermissionSqlType>("type"),
|
||||||
|
sql::Column<uint64_t>("id"),
|
||||||
|
sql::Column<ChannelId>("channelId"),
|
||||||
|
sql::Column<std::string>("permId"),
|
||||||
|
|
||||||
|
sql::Column<permission::PermissionValue>("value"),
|
||||||
|
sql::Column<permission::PermissionValue>("grant"),
|
||||||
|
sql::Column<bool>("flag_skip"),
|
||||||
|
sql::Column<bool>("flag_negate"),
|
||||||
|
};
|
||||||
|
|
||||||
|
for(auto& group : snapshot_data.parsed_channel_groups) {
|
||||||
|
GroupId group_id;
|
||||||
|
{
|
||||||
|
auto new_id = mappings.channel_group_id.find(group.group_id);
|
||||||
|
if(new_id == mappings.channel_group_id.end()) {
|
||||||
|
logWarning(logging_server_id, "Missing channel group mapping of group {}. Skipping permission insert.", group.group_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
group_id = new_id->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& permission : group.permissions) {
|
||||||
|
insert_query.add_entry(
|
||||||
|
target_server_id,
|
||||||
|
permission::SQL_PERM_GROUP,
|
||||||
|
group_id,
|
||||||
|
0,
|
||||||
|
permission.type->name,
|
||||||
|
permission.value.has_value ? permission.value.value : permNotGranted,
|
||||||
|
permission.granted.has_value ? permission.granted.value : permNotGranted,
|
||||||
|
permission.flag_skip,
|
||||||
|
permission.flag_negate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = insert_query.execute(this->handle->getSql(), true);
|
||||||
|
if(!result.failed_entries.empty())
|
||||||
|
logWarning(logging_server_id, "Failed to insert some channel group permissions into the database. Failed permission count: {}", result.failed_entries.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* register server & properties */
|
||||||
|
{
|
||||||
|
|
||||||
|
sql::InsertQuery insert_property_query{"properties",
|
||||||
|
sql::Column<ServerId>("serverId"),
|
||||||
|
sql::Column<property::PropertyType>("type"),
|
||||||
|
sql::Column<uint64_t>("id"),
|
||||||
|
sql::Column<std::string>("key"),
|
||||||
|
sql::Column<std::string>("value")
|
||||||
|
};
|
||||||
|
|
||||||
|
for(const auto& property : snapshot_data.parsed_server.properties.list_properties(property::FLAG_SAVE)) {
|
||||||
|
if(!property.isModified()) continue;
|
||||||
|
|
||||||
|
auto value = property.value();
|
||||||
|
switch(property.type().property_index) {
|
||||||
|
case property::VIRTUALSERVER_DEFAULT_SERVER_GROUP: {
|
||||||
|
auto new_id = mappings.server_group_id.find(property.as<GroupId>());
|
||||||
|
if(new_id == mappings.server_group_id.end()) {
|
||||||
|
logWarning(logging_server_id, "Missing server group mapping for group {}. {} will reference an invalid group.", property.value(), property.type().name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = std::to_string(new_id->second);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP:
|
||||||
|
case property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP: {
|
||||||
|
auto new_id = mappings.channel_group_id.find(property.as<GroupId>());
|
||||||
|
if(new_id == mappings.channel_group_id.end()) {
|
||||||
|
logWarning(logging_server_id, "Missing channel group mapping for group {}. {} will reference an invalid group.", property.value(), property.type().name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = std::to_string(new_id->second);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_property_query.add_entry(
|
||||||
|
target_server_id,
|
||||||
|
property::PROP_TYPE_SERVER,
|
||||||
|
0,
|
||||||
|
std::string{property.type().name},
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto result = insert_property_query.execute(this->handle->getSql(), true);
|
||||||
|
if(!result.failed_entries.empty())
|
||||||
|
logWarning(logging_server_id, "Failed to insert all server properties into the database. Failed property count: {}", result.failed_entries.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* client permissions */
|
/* client permissions */
|
||||||
{
|
{
|
||||||
sql::InsertQuery insert_query{"permissions",
|
sql::InsertQuery insert_query{"permissions",
|
||||||
@ -464,8 +584,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
|
|
||||||
for(auto& entry : snapshot_data.client_permissions) {
|
for(auto& entry : snapshot_data.client_permissions) {
|
||||||
{
|
{
|
||||||
auto new_id = client_id_mapping.find(entry.id1);
|
auto new_id = mappings.client_id.find(entry.id1);
|
||||||
if(new_id == client_id_mapping.end()) {
|
if(new_id == mappings.client_id.end()) {
|
||||||
logWarning(logging_server_id, "Missing client id mapping for client permission entry (client id: {}). Skipping permission insert.", entry.id1);
|
logWarning(logging_server_id, "Missing client id mapping for client permission entry (client id: {}). Skipping permission insert.", entry.id1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -509,8 +629,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
|
|
||||||
for(auto& entry : snapshot_data.client_channel_permissions) {
|
for(auto& entry : snapshot_data.client_channel_permissions) {
|
||||||
{
|
{
|
||||||
auto new_id = channel_id_mapping.find(entry.id1);
|
auto new_id = mappings.channel_id.find(entry.id1);
|
||||||
if(new_id == channel_id_mapping.end()) {
|
if(new_id == mappings.channel_id.end()) {
|
||||||
logWarning(logging_server_id, "Missing channel id mapping for client channel permission entry (client id: {}, channel id: {}). Skipping permission insert.", entry.id2, entry.id1);
|
logWarning(logging_server_id, "Missing channel id mapping for client channel permission entry (client id: {}, channel id: {}). Skipping permission insert.", entry.id2, entry.id1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -518,8 +638,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto new_id = client_id_mapping.find(entry.id2);
|
auto new_id = mappings.client_id.find(entry.id2);
|
||||||
if(new_id == client_id_mapping.end()) {
|
if(new_id == mappings.client_id.end()) {
|
||||||
logWarning(logging_server_id, "Missing client id mapping for client channel permission entry (client id: {}, channel id: {}). Skipping permission insert.", entry.id2, entry.id1);
|
logWarning(logging_server_id, "Missing client id mapping for client channel permission entry (client id: {}, channel id: {}). Skipping permission insert.", entry.id2, entry.id1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -546,6 +666,53 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
logWarning(logging_server_id, "Failed to insert all client channel permissions into the database. Failed permission count: {}", result.failed_entries.size());
|
logWarning(logging_server_id, "Failed to insert all client channel permissions into the database. Failed permission count: {}", result.failed_entries.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* music bots */
|
||||||
|
{
|
||||||
|
sql::InsertQuery insert_query{"musicbots",
|
||||||
|
sql::Column<ServerId>("serverId"),
|
||||||
|
sql::Column<ClientDbId>("botId"),
|
||||||
|
sql::Column<std::string>("uniqueId"),
|
||||||
|
sql::Column<ClientDbId>("owner"),
|
||||||
|
};
|
||||||
|
|
||||||
|
for(const auto& client : snapshot_data.music_bots) {
|
||||||
|
ClientDbId bot_id;
|
||||||
|
{
|
||||||
|
auto new_id = mappings.client_id.find(client.database_id);
|
||||||
|
if(new_id == mappings.client_id.end()) {
|
||||||
|
logWarning(logging_server_id, "Missing music bot database id mapping for bot {}. Skipping bot register.", client.unique_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bot_id = new_id->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientDbId bot_owner_id;
|
||||||
|
{
|
||||||
|
auto new_id = mappings.client_id.find(client.bot_owner_id);
|
||||||
|
if(new_id == mappings.client_id.end()) {
|
||||||
|
logWarning(logging_server_id, "Missing music bots owner database id mapping for bot {}. Resetting it to zero.", client.unique_id);
|
||||||
|
bot_owner_id = 0;
|
||||||
|
} else {
|
||||||
|
bot_owner_id = new_id->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_query.add_entry(
|
||||||
|
target_server_id,
|
||||||
|
bot_id,
|
||||||
|
client.unique_id,
|
||||||
|
bot_owner_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto result = insert_query.execute(this->handle->getSql(), true);
|
||||||
|
if(!result.failed_entries.empty())
|
||||||
|
logWarning(logging_server_id, "Failed to insert some music bots into the database. Failed bot count: {}", result.failed_entries.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* music bot attributes */
|
/* music bot attributes */
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -558,8 +725,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
};
|
};
|
||||||
|
|
||||||
for(auto& bot : snapshot_data.music_bots) {
|
for(auto& bot : snapshot_data.music_bots) {
|
||||||
auto new_id = client_id_mapping.find(bot.database_id);
|
auto new_id = mappings.client_id.find(bot.database_id);
|
||||||
if(new_id == client_id_mapping.end()) {
|
if(new_id == mappings.client_id.end()) {
|
||||||
logWarning(logging_server_id, "Missing client id mapping for music bot (Unique ID: {}). Skipping permission insert.", bot.unique_id);
|
logWarning(logging_server_id, "Missing client id mapping for music bot (Unique ID: {}). Skipping permission insert.", bot.unique_id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -571,8 +738,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
if(property.type() == property::CLIENT_LAST_CHANNEL) {
|
if(property.type() == property::CLIENT_LAST_CHANNEL) {
|
||||||
auto channel_id = property.as<ChannelId>();
|
auto channel_id = property.as<ChannelId>();
|
||||||
|
|
||||||
auto new_channel_id = channel_id_mapping.find(channel_id);
|
auto new_channel_id = mappings.channel_id.find(channel_id);
|
||||||
if(new_channel_id == channel_id_mapping.end()) {
|
if(new_channel_id == mappings.channel_id.end()) {
|
||||||
logWarning(logging_server_id, "Missing channel id mapping for channel {} for music bot {}.", channel_id, bot.unique_id);
|
logWarning(logging_server_id, "Missing channel id mapping for channel {} for music bot {}.", channel_id, bot.unique_id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -654,8 +821,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
|
|
||||||
for(auto& song : snapshot_data.playlist_songs) {
|
for(auto& song : snapshot_data.playlist_songs) {
|
||||||
{
|
{
|
||||||
auto new_id = client_id_mapping.find(song.invoker_id);
|
auto new_id = mappings.client_id.find(song.invoker_id);
|
||||||
if(new_id == client_id_mapping.end()) {
|
if(new_id == mappings.client_id.end()) {
|
||||||
logWarning(logging_server_id, "Missing client id mapping for playlist song invoker (Invoker ID: {}). Changing it to zero.", song.invoker_id);
|
logWarning(logging_server_id, "Missing client id mapping for playlist song invoker (Invoker ID: {}). Changing it to zero.", song.invoker_id);
|
||||||
song.invoker_id = 0;
|
song.invoker_id = 0;
|
||||||
} else {
|
} else {
|
||||||
@ -682,7 +849,6 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
|
|||||||
logWarning(logging_server_id, "Failed to insert some playlist songs into the database. Failed song count: {}", result.failed_entries.size());
|
logWarning(logging_server_id, "Failed to insert some playlist songs into the database. Failed song count: {}", result.failed_entries.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
@ -43,21 +43,28 @@ bool relation_parser::parse(std::string &error, group_relations &result, size_t
|
|||||||
|
|
||||||
bool key_found;
|
bool key_found;
|
||||||
while(offset < *relation_end) {
|
while(offset < *relation_end) {
|
||||||
|
ChannelId channel_id;
|
||||||
auto begin_bulk = this->command.bulk(offset);
|
auto begin_bulk = this->command.bulk(offset);
|
||||||
if(!begin_bulk.has_key("iid")) {
|
if(begin_bulk.has_key("iid")) {
|
||||||
error = "missing iid at character " + std::to_string(begin_bulk.command_character_index());
|
channel_id = begin_bulk.value_as<ChannelId>("iid");
|
||||||
return false;
|
} else {
|
||||||
|
/* may be the case for the global assignments (iid may be not send in the first few generations) */
|
||||||
|
channel_id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& relations = result[begin_bulk.value_as<ChannelId>("iid")];
|
auto& relations = result[channel_id];
|
||||||
auto next_iid = this->command.next_bulk_containing("iid", offset + 1);
|
auto next_iid = this->command.next_bulk_containing("iid", offset + 1);
|
||||||
|
|
||||||
if(next_iid.has_value() && *next_iid < relation_end)
|
size_t end_index;
|
||||||
|
if(next_iid.has_value() && *next_iid < relation_end) {
|
||||||
|
end_index = *next_iid;
|
||||||
relations.reserve(*next_iid - offset);
|
relations.reserve(*next_iid - offset);
|
||||||
else
|
} else {
|
||||||
|
end_index = *relation_end;
|
||||||
relations.reserve(*relation_end - offset);
|
relations.reserve(*relation_end - offset);
|
||||||
|
}
|
||||||
|
|
||||||
while(offset < *next_iid) {
|
while(offset < end_index) {
|
||||||
auto relation_data = this->command.bulk(offset++);
|
auto relation_data = this->command.bulk(offset++);
|
||||||
auto& relation = relations.emplace_back();
|
auto& relation = relations.emplace_back();
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ using namespace ts::server::snapshots;
|
|||||||
bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t &index) {
|
bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t &index) {
|
||||||
auto data = this->command[index];
|
auto data = this->command[index];
|
||||||
|
|
||||||
|
entry.properties.register_property_type<property::ClientProperties>();
|
||||||
|
|
||||||
size_t entry_index{0};
|
size_t entry_index{0};
|
||||||
std::string_view key{};
|
std::string_view key{};
|
||||||
std::string value{};
|
std::string value{};
|
||||||
@ -23,7 +25,12 @@ bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t
|
|||||||
} else if(key == "bot_unique_id") {
|
} else if(key == "bot_unique_id") {
|
||||||
entry.unique_id = value;
|
entry.unique_id = value;
|
||||||
} else if(key == "bot_owner_id") {
|
} else if(key == "bot_owner_id") {
|
||||||
entry.bot_owner_id = value;
|
char* end_ptr{nullptr};
|
||||||
|
entry.bot_owner_id = strtoull(value.c_str(), &end_ptr, 10);
|
||||||
|
if (*end_ptr) {
|
||||||
|
error = "failed to parse bot owner database id at character " + std::to_string(data.key_command_character_index(key) + key.length());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if(key == "end_bots") {
|
} else if(key == "end_bots") {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
@ -52,6 +59,8 @@ bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t
|
|||||||
bool playlist_parser::parse(std::string &error, playlist_entry &entry, size_t &index) {
|
bool playlist_parser::parse(std::string &error, playlist_entry &entry, size_t &index) {
|
||||||
auto data = this->command[index];
|
auto data = this->command[index];
|
||||||
|
|
||||||
|
entry.properties.register_property_type<property::PlaylistProperties>();
|
||||||
|
|
||||||
size_t entry_index{0};
|
size_t entry_index{0};
|
||||||
std::string_view key{};
|
std::string_view key{};
|
||||||
std::string value{};
|
std::string value{};
|
||||||
@ -82,7 +91,7 @@ bool playlist_parser::parse(std::string &error, playlist_entry &entry, size_t &i
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool playlist_song_parser::parse(std::string &error, playlist_song_entry &entry, size_t &index) {
|
bool playlist_song_parser::parse(std::string &error, playlist_song_entry &entry, size_t &index) {
|
||||||
|
@ -11,7 +11,7 @@ namespace ts::server::snapshots {
|
|||||||
ClientDbId database_id{0};
|
ClientDbId database_id{0};
|
||||||
|
|
||||||
std::string unique_id{};
|
std::string unique_id{};
|
||||||
std::string bot_owner_id{};
|
ClientDbId bot_owner_id{};
|
||||||
|
|
||||||
Properties properties{};
|
Properties properties{};
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
#include <Definitions.h>
|
#include <Definitions.h>
|
||||||
#include <log/LogUtils.h>
|
#include <log/LogUtils.h>
|
||||||
|
#include <misc/base64.h>
|
||||||
|
#include <misc/digest.h>
|
||||||
|
|
||||||
|
#include <zstd.h>
|
||||||
|
|
||||||
#include "./snapshot.h"
|
#include "./snapshot.h"
|
||||||
#include "./server.h"
|
#include "./server.h"
|
||||||
@ -13,12 +17,33 @@
|
|||||||
#include "./groups.h"
|
#include "./groups.h"
|
||||||
#include "./snapshot_data.h"
|
#include "./snapshot_data.h"
|
||||||
|
|
||||||
|
using namespace ts;
|
||||||
using namespace ts::server;
|
using namespace ts::server;
|
||||||
|
|
||||||
|
inline bool verify_hash(std::string &error, ServerId server_id, const command_parser &data, size_t hash_offset, const std::string_view& expected_hash) {
|
||||||
|
if(!expected_hash.empty() && !data.has_switch("ignorehash")) {
|
||||||
|
auto payload = data.payload_view(hash_offset);
|
||||||
|
if(auto switches{payload.find(" -")}; switches != std::string::npos) {
|
||||||
|
debugMessage(server_id, "Truncating the snapshot payload after index {} (First switch).", switches);
|
||||||
|
payload = payload.substr(0, switches);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto calculated_hash = base64::encode(digest::sha1(payload));
|
||||||
|
|
||||||
|
/* debugMessage(server_id, "SHA1 for playload: {}", payload); */
|
||||||
|
debugMessage(server_id, "Expected hash: {}, Calculated hash: {}", expected_hash, calculated_hash);
|
||||||
|
if(expected_hash != calculated_hash) {
|
||||||
|
error = "invalid snapshot hash.\nExpected: " + std::string{expected_hash} + "\nCalculated: " + calculated_hash;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool snapshots::parse_snapshot(snapshot_data &result, std::string &error, ServerId server_id, const command_parser &data) {
|
bool snapshots::parse_snapshot(snapshot_data &result, std::string &error, ServerId server_id, const command_parser &data) {
|
||||||
if(data.bulk(0).has_key("version")) {
|
if(data.bulk(0).has_key("version")) {
|
||||||
return snapshots::parse_snapshot_ts3(result, error, server_id, data);
|
return snapshots::parse_snapshot_ts3(result, error, server_id, data);
|
||||||
} else if(data.bulk(1).has_key("snapshot_version")) {
|
} else if(data.bulk(0).has_key("snapshot_version") || (data.bulk_count() > 1 && data.bulk(1).has_key("snapshot_version"))) {
|
||||||
/* teaspeak snapshot */
|
/* teaspeak snapshot */
|
||||||
return snapshots::parse_snapshot_teaspeak(result, error, server_id, data);
|
return snapshots::parse_snapshot_teaspeak(result, error, server_id, data);
|
||||||
} else {
|
} else {
|
||||||
@ -27,24 +52,83 @@ bool snapshots::parse_snapshot(snapshot_data &result, std::string &error, Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool snapshots::parse_snapshot_teaspeak(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
inline std::unique_ptr<char, void(*)(void*)> decompress_snapshot(std::string& error, size_t& decompressed_length, const std::string& data) {
|
||||||
if(!data.bulk(1).has_key("snapshot_version")) {
|
std::unique_ptr<char, void(*)(void*)> result{nullptr, ::free};
|
||||||
error = "Missing snapshot version";
|
|
||||||
return false;
|
auto uncompressed_length = ZSTD_getFrameContentSize(data.data(), data.length());
|
||||||
|
if(uncompressed_length == ZSTD_CONTENTSIZE_UNKNOWN) {
|
||||||
|
error = "unknown uncompressed size";
|
||||||
|
return result;
|
||||||
|
} else if(uncompressed_length == ZSTD_CONTENTSIZE_ERROR) {
|
||||||
|
error = "failed to calculate uncompressed size";
|
||||||
|
return result;
|
||||||
|
} else if(ZSTD_isError(uncompressed_length)) {
|
||||||
|
error = "failed to calculate uncompressed size: " + std::string{ZSTD_getErrorName(uncompressed_length)};
|
||||||
|
return result;
|
||||||
|
} else if(uncompressed_length > 512 * 1024 * 1028) {
|
||||||
|
error = "uncompressed size exceeds half a gigabyte!";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.reset((char*) ::malloc(uncompressed_length));
|
||||||
|
if(!result) {
|
||||||
|
error = "failed to allocate decompress buffer";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed_length = ZSTD_decompress(&*result, uncompressed_length, data.data(), data.length());
|
||||||
|
if(ZSTD_isError(decompressed_length)) {
|
||||||
|
error = "decompress error occurred: " + std::string{ZSTD_getErrorName(decompressed_length)};
|
||||||
|
result = nullptr;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool snapshots::parse_snapshot_teaspeak(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
||||||
|
snapshots::version_t version;
|
||||||
|
if(data.bulk(0).has_key("snapshot_version")) {
|
||||||
|
version = data.bulk(0).value_as<snapshots::version_t>("snapshot_version");
|
||||||
|
} else {
|
||||||
|
version = data.bulk(1).value_as<snapshots::version_t>("snapshot_version");
|
||||||
}
|
}
|
||||||
auto version = data.bulk(1).value_as<snapshots::version_t>("snapshot_version");
|
|
||||||
auto hash = data.bulk(0).value("hash");
|
|
||||||
|
|
||||||
if(version < 1) {
|
if(version < 1) {
|
||||||
error = "snapshot version too old";
|
error = "snapshot version too old";
|
||||||
return false;
|
return false;
|
||||||
|
} else if(version == 2) {
|
||||||
|
auto hash = data.bulk(0).value("hash");
|
||||||
|
|
||||||
|
if(!verify_hash(error, server_id, data, 1, hash))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* the actual snapshot begins at index 2 */
|
||||||
|
return snapshots::parse_snapshot_raw(result, error, server_id, data, 2, type::TEASPEAK, version);
|
||||||
} else if(version > 2) {
|
} else if(version > 2) {
|
||||||
error = "snapshot version is too new";
|
auto compressed_data{base64::decode(data.value("data"))};
|
||||||
|
|
||||||
|
size_t decompressed_length;
|
||||||
|
auto decompressed = decompress_snapshot(error, decompressed_length, compressed_data);
|
||||||
|
if(!decompressed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* the actual snapshot begins at index 2 */
|
std::string_view decompressed_data{&*decompressed, decompressed_length};
|
||||||
return snapshots::parse_snapshot_raw(result, error, server_id, data, hash, 2, type::TEASPEAK, version);
|
ts::command_parser decompressed_parser{decompressed_data};
|
||||||
|
if(!decompressed_parser.parse(false)) {
|
||||||
|
error = "failed to parse snapshot payload";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshots::parse_snapshot_raw(result, error, server_id, decompressed_parser, 0, type::TEASPEAK, version);
|
||||||
|
} else if(version > 3) {
|
||||||
|
error = "snapshot version is too new";
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
error = "invalid snapshot version";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
||||||
@ -52,24 +136,42 @@ bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts
|
|||||||
if(data.bulk(0).has_key("version"))
|
if(data.bulk(0).has_key("version"))
|
||||||
version = data.bulk(0).value_as<snapshots::version_t>("version");
|
version = data.bulk(0).value_as<snapshots::version_t>("version");
|
||||||
|
|
||||||
auto hash = data.bulk(0).value("hash");
|
|
||||||
if(data.bulk(0).has_key("salt")) {
|
if(data.bulk(0).has_key("salt")) {
|
||||||
error = "TeaSpeak dosn't support encrypted snapshots yet";
|
error = "TeaSpeak dosn't support encrypted snapshots yet";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(version == 0) {
|
if(version == 0) {
|
||||||
return snapshots::parse_snapshot_raw(result, error, server_id, data, hash, 1, type::TEAMSPEAK, version);
|
auto hash = data.bulk(0).value("hash");
|
||||||
|
if(!verify_hash(error, server_id, data, 1, hash))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return snapshots::parse_snapshot_raw(result, error, server_id, data, 1, type::TEAMSPEAK, version);
|
||||||
} else if(version == 1) {
|
} else if(version == 1) {
|
||||||
error = "version 1 is an invalid version";
|
error = "version 1 is an invalid version";
|
||||||
return false;
|
return false;
|
||||||
} else if(version == 2) {
|
} else if(version >= 2 && version <= 3) {
|
||||||
/* compressed data */
|
std::string compressed_data;
|
||||||
error = "version 2 isn't currently supported";
|
if(version == 2) {
|
||||||
|
compressed_data = base64::decode(data.payload_view(1));
|
||||||
|
} else {
|
||||||
|
compressed_data = base64::decode(data.value("data"));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t decompressed_length;
|
||||||
|
auto decompressed = decompress_snapshot(error, decompressed_length, compressed_data);
|
||||||
|
if(!decompressed) {
|
||||||
return false;
|
return false;
|
||||||
} else if(version == 3) {
|
}
|
||||||
error = "version 3 isn't currently supported";
|
|
||||||
|
std::string_view decompressed_data{&*decompressed, decompressed_length};
|
||||||
|
ts::command_parser decompressed_parser{decompressed_data};
|
||||||
|
if(!decompressed_parser.parse(false)) {
|
||||||
|
error = "failed to parse snapshot payload";
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshots::parse_snapshot_raw(result, error, server_id, decompressed_parser, 0, type::TEAMSPEAK, version);
|
||||||
} else {
|
} else {
|
||||||
error = "snapshots with version 1-3 are currently supported";
|
error = "snapshots with version 1-3 are currently supported";
|
||||||
return false;
|
return false;
|
||||||
@ -77,9 +179,7 @@ bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &error, ServerId server_id, const command_parser &command,
|
bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &error, ServerId server_id, const command_parser &command,
|
||||||
const std::string &hash, size_t command_offset, snapshots::type type, snapshots::version_t version) {
|
size_t command_offset, snapshots::type type, snapshots::version_t version) {
|
||||||
//TODO: Verify hash
|
|
||||||
|
|
||||||
/* all snapshots start with the virtual server properties */
|
/* all snapshots start with the virtual server properties */
|
||||||
{
|
{
|
||||||
snapshots::server_parser parser{type, version, command};
|
snapshots::server_parser parser{type, version, command};
|
||||||
@ -105,7 +205,6 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
snapshot_data.parsed_channels.reserve(*end_bulk - command_offset);
|
snapshot_data.parsed_channels.reserve(*end_bulk - command_offset);
|
||||||
debugMessage(server_id, "Snapshot contains {} channels", *end_bulk - command_offset);
|
|
||||||
|
|
||||||
while(!command.bulk(command_offset).has_key("end_channels")) {
|
while(!command.bulk(command_offset).has_key("end_channels")) {
|
||||||
auto& entry = snapshot_data.parsed_channels.emplace_back();
|
auto& entry = snapshot_data.parsed_channels.emplace_back();
|
||||||
@ -130,7 +229,6 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
snapshot_data.parsed_channels.reserve(*end_bulk - command_offset);
|
snapshot_data.parsed_channels.reserve(*end_bulk - command_offset);
|
||||||
debugMessage(server_id, "Snapshot contains {} clients", *end_bulk - command_offset);
|
|
||||||
|
|
||||||
while(!command.bulk(command_offset).has_key("end_clients")) {
|
while(!command.bulk(command_offset).has_key("end_clients")) {
|
||||||
auto& entry = snapshot_data.parsed_clients.emplace_back();
|
auto& entry = snapshot_data.parsed_clients.emplace_back();
|
||||||
@ -140,6 +238,93 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
|
|||||||
command_offset++; /* the "end_clients" token */
|
command_offset++; /* the "end_clients" token */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(type == snapshots::type::TEASPEAK && version >= 2) {
|
||||||
|
|
||||||
|
bool music_bots_parsed{false},
|
||||||
|
playlist_parsed{false},
|
||||||
|
playlist_songs_parsed{false};
|
||||||
|
|
||||||
|
if(!command.bulk(command_offset).has_key("begin_music")) {
|
||||||
|
error = "missing begin music key";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(!command.bulk(command_offset).has_key("end_music")) {
|
||||||
|
if(command.bulk(command_offset).has_key("begin_bots")) {
|
||||||
|
if(std::exchange(music_bots_parsed, true)) {
|
||||||
|
error = "duplicated music bot list";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshots::music_bot_parser parser{type, version, command};
|
||||||
|
while(!command.bulk(command_offset).has_key("end_bots")) {
|
||||||
|
auto& bot = snapshot_data.music_bots.emplace_back();
|
||||||
|
|
||||||
|
if(!parser.parse(error, bot, command_offset))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
command_offset++;
|
||||||
|
}
|
||||||
|
} else if(command.bulk(command_offset).has_key("begin_playlist")) {
|
||||||
|
if(std::exchange(playlist_parsed, true)) {
|
||||||
|
error = "duplicated playlist list";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshots::playlist_parser parser{type, version, command};
|
||||||
|
while(!command.bulk(command_offset).has_key("end_playlist")) {
|
||||||
|
auto& playlist = snapshot_data.playlists.emplace_back();
|
||||||
|
|
||||||
|
if(!parser.parse(error, playlist, command_offset))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
command_offset++;
|
||||||
|
}
|
||||||
|
} else if(command.bulk(command_offset).has_key("begin_playlist_songs")) {
|
||||||
|
if(std::exchange(playlist_songs_parsed, true)) {
|
||||||
|
error = "duplicated playlist songs list";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshots::playlist_song_parser parser{type, version, command};
|
||||||
|
|
||||||
|
PlaylistId current_playlist_id{0};
|
||||||
|
while(!command.bulk(command_offset).has_key("end_playlist_songs")) {
|
||||||
|
if(snapshot_data.playlist_songs.empty() || command.bulk(command_offset).has_key("song_playlist_id"))
|
||||||
|
current_playlist_id = command.bulk(command_offset).value_as<PlaylistId>("song_playlist_id");
|
||||||
|
|
||||||
|
auto& playlist_song = snapshot_data.playlist_songs.emplace_back();
|
||||||
|
playlist_song.playlist_id = current_playlist_id;
|
||||||
|
|
||||||
|
if(!parser.parse(error, playlist_song, command_offset))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
command_offset++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
__asm__("nop");
|
||||||
|
}
|
||||||
|
command_offset++;
|
||||||
|
}
|
||||||
|
command_offset++;
|
||||||
|
|
||||||
|
/* check if everything has been parsed */
|
||||||
|
if(!music_bots_parsed) {
|
||||||
|
error = "missing music bots";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!playlist_parsed) {
|
||||||
|
error = "missing playlists";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!playlist_songs_parsed) {
|
||||||
|
error = "missing playlist songs";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* permissions */
|
/* permissions */
|
||||||
{
|
{
|
||||||
bool server_groups_parsed{false},
|
bool server_groups_parsed{false},
|
||||||
@ -274,82 +459,14 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type == snapshots::type::TEASPEAK && version >= 2) {
|
debugMessage(server_id, "Parsed snapshot containing {} channels, {} server groups, {} channel groups, {} clients, {} music bots and {} playlists.",
|
||||||
|
snapshot_data.parsed_channels.size(),
|
||||||
bool music_bots_parsed{false},
|
snapshot_data.parsed_server_groups.size(),
|
||||||
playlist_parsed{false},
|
snapshot_data.parsed_channel_groups.size(),
|
||||||
playlist_songs_parsed{false};
|
snapshot_data.parsed_clients.size(),
|
||||||
|
snapshot_data.music_bots.size(),
|
||||||
if(!command.bulk(command_offset++).has_key("begin_music")) {
|
snapshot_data.playlists.size()
|
||||||
error = "missing begin music key";
|
);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while(!command.bulk(command_offset).has_key("end_music")) {
|
|
||||||
if(command.bulk(command_offset).has_key("begin_bots")) {
|
|
||||||
if(std::exchange(music_bots_parsed, true)) {
|
|
||||||
error = "duplicated music bot list";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshots::music_bot_parser parser{type, version, command};
|
|
||||||
while(!command.bulk(command_offset).has_key("end_bots")) {
|
|
||||||
auto& bot = snapshot_data.music_bots.emplace_back();
|
|
||||||
|
|
||||||
if(!parser.parse(error, bot, command_offset))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if(command.bulk(command_offset).has_key("begin_playlist")) {
|
|
||||||
if(std::exchange(playlist_parsed, true)) {
|
|
||||||
error = "duplicated playlist list";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshots::playlist_parser parser{type, version, command};
|
|
||||||
while(!command.bulk(command_offset).has_key("end_playlist")) {
|
|
||||||
auto& playlist = snapshot_data.playlists.emplace_back();
|
|
||||||
|
|
||||||
if(!parser.parse(error, playlist, command_offset))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if(command.bulk(command_offset).has_key("begin_playlist_songs")) {
|
|
||||||
if(std::exchange(playlist_songs_parsed, true)) {
|
|
||||||
error = "duplicated playlist songs list";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshots::playlist_song_parser parser{type, version, command};
|
|
||||||
|
|
||||||
PlaylistId current_playlist_id{0};
|
|
||||||
while(!command.bulk(command_offset).has_key("end_playlist_songs")) {
|
|
||||||
if(snapshot_data.playlist_songs.empty() || command.bulk(command_offset).has_key("song_playlist_id"))
|
|
||||||
current_playlist_id = command.bulk(command_offset).value_as<PlaylistId>("song_playlist_id");
|
|
||||||
|
|
||||||
auto& playlist_song = snapshot_data.playlist_songs.emplace_back();
|
|
||||||
playlist_song.playlist_id = current_playlist_id;
|
|
||||||
|
|
||||||
if(!parser.parse(error, playlist_song, command_offset))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check if everything has been parsed */
|
|
||||||
if(!music_bots_parsed) {
|
|
||||||
error = "missing music bots";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!playlist_parsed) {
|
|
||||||
error = "missing playlists";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!playlist_songs_parsed) {
|
|
||||||
error = "missing playlist songs";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
@ -143,7 +143,7 @@ bool permission_parser::parse_entry_teaspeak_v1(std::string &error, std::vector<
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
permission::PermissionValue value{};
|
permission::PermissionValue value;
|
||||||
{
|
{
|
||||||
auto value_string = data.value("value", key_found);
|
auto value_string = data.value("value", key_found);
|
||||||
if(!key_found) {
|
if(!key_found) {
|
||||||
@ -159,7 +159,7 @@ bool permission_parser::parse_entry_teaspeak_v1(std::string &error, std::vector<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
permission::PermissionValue granted{};
|
permission::PermissionValue granted;
|
||||||
{
|
{
|
||||||
auto value_string = data.value("grant", key_found);
|
auto value_string = data.value("grant", key_found);
|
||||||
if(!key_found) {
|
if(!key_found) {
|
||||||
|
@ -54,6 +54,6 @@ namespace ts::server::snapshots {
|
|||||||
extern bool parse_snapshot_ts3(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
extern bool parse_snapshot_ts3(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||||
extern bool parse_snapshot_teaspeak(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
extern bool parse_snapshot_teaspeak(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||||
|
|
||||||
extern bool parse_snapshot_raw(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */, const std::string& /* hash */, size_t /* offset */, snapshots::type /* type */, snapshots::version_t /* version */);
|
extern bool parse_snapshot_raw(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */, size_t /* offset */, snapshots::type /* type */, snapshots::version_t /* version */);
|
||||||
|
|
||||||
}
|
}
|
@ -29,4 +29,28 @@ namespace ts::server::snapshots {
|
|||||||
std::deque<snapshots::playlist_entry> playlists{};
|
std::deque<snapshots::playlist_entry> playlists{};
|
||||||
std::deque<snapshots::playlist_song_entry> playlist_songs{};
|
std::deque<snapshots::playlist_song_entry> playlist_songs{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct snapshot_mappings {
|
||||||
|
ServerId new_server_id{};
|
||||||
|
|
||||||
|
std::map<ClientDbId, ClientDbId> client_id{};
|
||||||
|
std::map<ChannelId, ChannelId> channel_id{};
|
||||||
|
std::map<ChannelId, ChannelId> channel_group_id{};
|
||||||
|
std::map<ChannelId, ChannelId> server_group_id{};
|
||||||
|
std::map<PlaylistId, PlaylistId> playlist_id{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct snapshot_deploy_timings {
|
||||||
|
std::chrono::system_clock::time_point initial_timestamp{};
|
||||||
|
std::chrono::system_clock::time_point decode_timestamp{}; /* end decode */
|
||||||
|
std::chrono::system_clock::time_point parse_timestamp{}; /* end parse */
|
||||||
|
std::chrono::system_clock::time_point deploy_timestamp{}; /* else deploy */
|
||||||
|
std::chrono::system_clock::time_point startup_timestamp{}; /* end startup */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct snapshot_deploy {
|
||||||
|
snapshot_deploy_timings timings{};
|
||||||
|
snapshot_mappings mappings{};
|
||||||
|
snapshot_data data{};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
2
shared
2
shared
@ -1 +1 @@
|
|||||||
Subproject commit a6343968450d4e626bcf99ec622b4a325bf8c4ba
|
Subproject commit c3188cd9e5f499f4b5813953d2bc62f56800222f
|
Loading…
x
Reference in New Issue
Block a user