First draft of the new snapshot system and database changes
This commit is contained in:
parent
c60119af00
commit
72abd7e20e
@ -1 +1 @@
|
||||
Subproject commit d75ecb67041c3b9958d75848c253e35774d36578
|
||||
Subproject commit 0dc8e28125f9727f2dffef4b6f93b397099db1f0
|
@ -139,6 +139,8 @@ set(SERVER_SOURCE_FILES
|
||||
src/snapshots/server.cpp
|
||||
src/snapshots/groups.cpp
|
||||
src/snapshots/deploy.cpp
|
||||
src/snapshots/music.cpp
|
||||
src/snapshots/parser.cpp
|
||||
|
||||
src/manager/ActionLogger.cpp
|
||||
src/manager/ActionLoggerImpl.cpp
|
||||
|
@ -48,98 +48,97 @@ void DatabaseHelper::tick() {
|
||||
}
|
||||
}
|
||||
|
||||
int collectData(deque<shared_ptr<ClientDatabaseInfo>>* list, int length, char** values, char** columns){
|
||||
shared_ptr<ClientDatabaseInfo> entry = std::make_shared<ClientDatabaseInfo>();
|
||||
constexpr static std::string_view kSqlBase{"SELECT `client_unique_id`, `client_database_id`, `client_nickname`, `client_created`, `client_last_connected`, `client_total_connections` FROM `clients_server`"};
|
||||
inline std::deque<std::shared_ptr<ClientDatabaseInfo>> query_database_client_info(sql::SqlManager* sql_manager, ServerId server_id, const std::string& query, const std::vector<variable>& variables) {
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> result{};
|
||||
|
||||
for(int index = 0; index < length; index++)
|
||||
if(strcmp(columns[index], "cldbid") == 0)
|
||||
entry->cldbid = static_cast<ClientDbId>(stol(values[index]));
|
||||
else if(strcmp(columns[index], "clientUid") == 0)
|
||||
entry->uniqueId = values[index];
|
||||
else if(strcmp(columns[index], "firstConnect") == 0)
|
||||
entry->created = time_point<system_clock>() + seconds(stoll(values[index]));
|
||||
else if(strcmp(columns[index], "lastConnect") == 0)
|
||||
entry->lastjoin = time_point<system_clock>() + seconds(stoll(values[index]));
|
||||
else if(strcmp(columns[index], "connections") == 0)
|
||||
entry->connections = static_cast<uint32_t>(stoi(values[index]));
|
||||
else if(strcmp(columns[index], "lastName") == 0)
|
||||
entry->lastName = values[index];
|
||||
else if(strcmp(columns[index], "serverId") == 0);
|
||||
else logError(LOG_GENERAL, "Invalid db key for manager data. Key: {}", columns[index]);
|
||||
sql::command command{sql_manager, query};
|
||||
for(const auto& variable : variables)
|
||||
command.value(variable);
|
||||
auto sql_result = command.query([&](int length, std::string* values, std::string* names) {
|
||||
auto entry = std::make_shared<ClientDatabaseInfo>();
|
||||
|
||||
list->push_back(entry);
|
||||
return 0;
|
||||
}
|
||||
auto index{0};
|
||||
try {
|
||||
assert(names[index] == "client_unique_id");
|
||||
entry->client_unique_id = values[index++];
|
||||
|
||||
#define MAX_QUERY 32
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<VirtualServer>& server, const std::deque<ClientDbId>& list) {
|
||||
if(list.empty()) return {};
|
||||
assert(names[index] == "client_database_id");
|
||||
entry->client_database_id = std::stoull(values[index++]);
|
||||
|
||||
deque<shared_ptr<ClientDatabaseInfo>> result;
|
||||
assert(names[index] == "client_nickname");
|
||||
entry->client_nickname = values[index++];
|
||||
|
||||
if(list.size() <= MAX_QUERY){
|
||||
std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND (";
|
||||
for(auto elm : list)
|
||||
query += " `cldbid` = " + to_string(elm) + " OR";
|
||||
query = query.substr(0, query.length() - 3) + ")";
|
||||
logTrace(server ? server->getServerId() : 0, "[SQL] queryDatabseInfo(...) -> {}", query);
|
||||
auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(std::function<decltype(collectData)>(collectData), &result);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(state);
|
||||
if(!state) return {};
|
||||
} else {
|
||||
std::deque<ClientDbId> sub;
|
||||
do {
|
||||
sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
assert(names[index] == "client_created");
|
||||
entry->client_created = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
|
||||
|
||||
auto res = this->queryDatabaseInfo(server, sub);
|
||||
result.insert(result.end(), res.begin(), res.end());
|
||||
sub.clear();
|
||||
} while(!list.empty());
|
||||
assert(names[index] == "client_last_connected");
|
||||
entry->client_last_connected = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
entry->client_total_connections = std::stoull(values[index++]);
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
logError(server_id, "Failed to parse client base properties at index {}: {}. Query: {}",
|
||||
index - 1,
|
||||
ex.what(),
|
||||
query
|
||||
);
|
||||
}
|
||||
|
||||
result.push_back(std::move(entry));
|
||||
});
|
||||
if(!sql_result) {
|
||||
logError(server_id, "Failed to query client database infos: {}; Query: {}", sql_result.fmtStr(), query);
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<VirtualServer>& server, const std::deque<ClientDbId>& list) {
|
||||
if(list.empty())
|
||||
return {};
|
||||
|
||||
std::string valueList{};
|
||||
for(const auto& element : list)
|
||||
valueList += ", " + std::to_string(element);
|
||||
valueList = valueList.substr(2);
|
||||
|
||||
auto serverId = server ? server->getServerId() : 0;
|
||||
return query_database_client_info(this->sql, serverId, std::string{kSqlBase} + "WHERE `server_id` = :sid AND `client_database_id` IN (" + valueList + ")", {variable{":sid", serverId}});
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &server, std::deque<std::string> list) {
|
||||
if(list.empty()) return {};
|
||||
if(list.empty())
|
||||
return {};
|
||||
|
||||
deque<shared_ptr<ClientDatabaseInfo>> result;
|
||||
std::string valueList{};
|
||||
for(size_t value_index{0}; value_index < list.size(); value_index++)
|
||||
valueList += ", :v" + std::to_string(value_index);
|
||||
valueList = valueList.substr(2);
|
||||
|
||||
if(list.size() <= MAX_QUERY){
|
||||
std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND (";
|
||||
for(const auto &elm : list)
|
||||
query += " `clientUid` = '" + elm + "' OR";
|
||||
query = query.substr(0, query.length() - 3) + ")";
|
||||
logTrace(server ? server->getServerId() : 0, "[SQL] queryDatabseInfoByUid(...) -> {}", query);
|
||||
auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(function<decltype(collectData)>(collectData), &result);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(state);
|
||||
if(!state) return {};
|
||||
} else {
|
||||
std::deque<std::string> sub;
|
||||
do {
|
||||
sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
auto serverId = server ? server->getServerId() : 0;
|
||||
|
||||
auto res = this->queryDatabaseInfoByUid(server, sub);
|
||||
result.insert(result.end(), res.begin(), res.end());
|
||||
sub.clear();
|
||||
} while(!list.empty());
|
||||
}
|
||||
std::vector<variable> values{};
|
||||
values.reserve(list.size() + 1);
|
||||
|
||||
return result;
|
||||
values.emplace_back(":sid", serverId);
|
||||
for(size_t value_index{0}; value_index < list.size(); value_index++)
|
||||
values.emplace_back(":v" + std::to_string(value_index), list[value_index]);
|
||||
|
||||
return query_database_client_info(this->sql, serverId, std::string{kSqlBase} + "WHERE `server_id` = :sid AND `client_unique_id` IN (" + valueList + ")", values);
|
||||
}
|
||||
|
||||
bool DatabaseHelper::validClientDatabaseId(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) { return cldbid > 0; } //TODO here check
|
||||
|
||||
void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
||||
ServerId sid = static_cast<ServerId>(server ? server->getServerId() : 0);
|
||||
auto serverId = (ServerId) (server ? server->getServerId() : 0);
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(permManagerLock);
|
||||
for(auto permMgr : this->cachedPermissionManagers)
|
||||
if(permMgr->cldbid == cldbid && permMgr->sid == sid) {
|
||||
if(serverId == 0 || (permMgr->cldbid == cldbid && permMgr->sid == serverId)) {
|
||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
|
||||
delete permMgr;
|
||||
break;
|
||||
@ -147,12 +146,19 @@ void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server,
|
||||
}
|
||||
//TODO remove from props cache?
|
||||
|
||||
sql::result state{};
|
||||
|
||||
auto state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", sid}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", sid}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `clients` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", serverId}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", serverId}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
|
||||
|
||||
if(serverId == 0) {
|
||||
state = sql::command(this->sql, "DELETE FROM `clients_server` WHERE `server_id` = :sid AND `client_database_id` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
|
||||
} else {
|
||||
state = sql::command(this->sql, "DELETE FROM `clients_server` WHERE `client_database_id` = :id", variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `clients` WHERE `client_database_id` = :id", variable{":id", cldbid}).execute();
|
||||
}
|
||||
|
||||
//TODO delete letters
|
||||
//TODO delete query
|
||||
@ -560,52 +566,44 @@ std::shared_ptr<Properties> DatabaseHelper::default_properties_client(std::share
|
||||
return properties;
|
||||
}
|
||||
|
||||
std::mutex DatabaseHelper::database_id_mutex{};
|
||||
|
||||
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::shared_ptr<DataClient> cl) {
|
||||
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId serverId, std::shared_ptr<DataClient> cl) {
|
||||
cl->loadDataForCurrentServer();
|
||||
if(cl->getClientDatabaseId() == 0){ //Client does not exist
|
||||
ClientDbId new_client_database_id{0};
|
||||
auto res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 AND `clientUid` = :cluid", variable{":cluid", cl->getUid()}).query([](ClientDbId* ptr, int length, char** values, char** names){
|
||||
*ptr = static_cast<ClientDbId>(stoll(values[0]));
|
||||
return 0;
|
||||
}, &new_client_database_id);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(res);
|
||||
if(!res) return false;
|
||||
if(cl->getClientDatabaseId() == 0) {
|
||||
/* client does not exists, create a new one */
|
||||
|
||||
auto insertTemplate = sql::model(sql, "INSERT INTO `clients` (`serverId`, `cldbId`, `clientUid`, `lastName`,`firstConnect`,`lastConnect`, `connections`) VALUES (:serverId, :cldbid, :cluid, :name, :fconnect, :lconnect, :connections)",
|
||||
variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()},
|
||||
variable{":fconnect", duration_cast<seconds>(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0},
|
||||
variable{":connections", 0});
|
||||
if(new_client_database_id == 0) { /* we've a completely new user */
|
||||
std::lock_guard db_id_lock{DatabaseHelper::database_id_mutex};
|
||||
res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([&](int length, std::string* values, std::string* names) {
|
||||
assert(length == 1);
|
||||
new_client_database_id = (ClientDbId) stoll(values[0]);
|
||||
});
|
||||
LOG_SQL_CMD(res);
|
||||
if(!res) return false;
|
||||
sql::result sql_result{};
|
||||
|
||||
new_client_database_id += 1;
|
||||
res = insertTemplate.command().values(variable{":serverId", 0}, variable{":cldbid", new_client_database_id}).execute(); //Insert global
|
||||
LOG_SQL_CMD(res);
|
||||
if(!res) return false;
|
||||
debugMessage(LOG_INSTANCE, "Registered a new client. Unique id: {}, First server: {}, Database ID: {}", cl->getUid(), id, new_client_database_id);
|
||||
} else {
|
||||
debugMessage(id, "Having new client, which is already known on this instance. Unique id: {}, First server: {}, Database ID: {}", cl->getUid(), id, new_client_database_id);
|
||||
std::string insert_or_ignore{sql->getType() == sql::TYPE_SQLITE ? "INSERT OR IGNORE" : "INSERT IGNORE"};
|
||||
|
||||
auto currentTimeSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
sql_result = sql::command{sql, insert_or_ignore + " INTO `clients` (`client_unique_id`, `client_created`) VALUES (:uniqueId, :now)",
|
||||
variable{":uniqueId", cl->getUid()},
|
||||
variable{":now", currentTimeSeconds}
|
||||
}.execute();
|
||||
|
||||
if(!sql_result) {
|
||||
logCritical(LOG_INSTANCE, "Failed to execute client insert command for {}: {}", cl->getUid(), sql_result.fmtStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
if(id != 0){ //Else already inserted
|
||||
res = insertTemplate.command().values(variable{":serverId", id}, variable{":cldbid", new_client_database_id}).execute();
|
||||
pf(res);
|
||||
if(!res) return false;
|
||||
sql_result = sql::command{sql, "INSERT INTO `clients_server` (`server_id`, `client_unique_id`, `client_database_id`, `client_created`) SELECT :serverId, :uniqueId, `client_database_id`, :now FROM `clients` WHERE `client_unique_id` = :uniqueId;",
|
||||
variable{":serverId", serverId},
|
||||
variable{":uniqueId", cl->getUid()},
|
||||
variable{":now", currentTimeSeconds}
|
||||
}.execute();
|
||||
|
||||
if(!sql_result) {
|
||||
logCritical(LOG_INSTANCE, "Failed to execute client server insert command for {}: {}", cl->getUid(), sql_result.fmtStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
return assignDatabaseId(sql, id, cl);
|
||||
if(!cl->loadDataForCurrentServer())
|
||||
return false;
|
||||
|
||||
debugMessage(serverId, "Successfully registered client {} for server {} with database id {}.", cl->getUid(), serverId, cl->getClientDatabaseId());
|
||||
return true;
|
||||
}
|
||||
|
||||
logTrace(id, "Loaded client successfully from database. Database id: {} Unique id: {}", cl->getClientDatabaseId(), cl->getUid());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -886,6 +884,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
if(server) {
|
||||
props->operator[](property::CLIENT_DESCRIPTION) = server->properties()[property::VIRTUALSERVER_DEFAULT_CLIENT_DESCRIPTION].value();
|
||||
}
|
||||
|
||||
bool loaded = false;
|
||||
if(use_startup_cache && server) {
|
||||
shared_ptr<StartupCacheEntry> entry;
|
||||
@ -910,6 +909,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!loaded) {
|
||||
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type1", property::PropertyType::PROP_TYPE_CONNECTION}, variable{":type2", property::PropertyType::PROP_TYPE_CLIENT}, variable{":id", cldbid});
|
||||
|
||||
@ -943,15 +943,15 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
if(!prop.hasDbReference() && (prop.default_value() == prop.value())) return; //No changes to default value
|
||||
prop.setModified(false);
|
||||
|
||||
std::string sql;
|
||||
std::string sqlCommand;
|
||||
if(prop.hasDbReference())
|
||||
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key";
|
||||
sqlCommand = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key";
|
||||
else {
|
||||
prop.setDbReference(true);
|
||||
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
|
||||
sqlCommand = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
|
||||
}
|
||||
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + std::string{prop.type().name} + " value: " + prop.value());
|
||||
sql::command(this->sql, sql,
|
||||
sql::command(this->sql, sqlCommand,
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":type", prop.type().type_property},
|
||||
variable{":id", cldbid},
|
||||
@ -967,16 +967,51 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
return;
|
||||
}
|
||||
|
||||
std::string query;
|
||||
if(prop.type() == property::CLIENT_TOTALCONNECTIONS)
|
||||
query = "UPDATE `clients` SET `connections` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
|
||||
else if(prop.type() == property::CLIENT_NICKNAME)
|
||||
query = "UPDATE `clients` SET `lastName` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
|
||||
else if(prop.type() == property::CLIENT_LASTCONNECTED)
|
||||
query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
|
||||
if(query.empty()) return;
|
||||
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '" + std::string{prop.type().name} + "' for " + to_string(cldbid) + " (New value: " + prop.value() + ", SQL: " + query + ")");
|
||||
sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":cldbid", cldbid}, variable{":value", prop.value()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
std::string column;
|
||||
switch (prop.type().property_index) {
|
||||
case property::CLIENT_NICKNAME:
|
||||
column = "client_nickname";
|
||||
break;
|
||||
|
||||
case property::CLIENT_LASTCONNECTED:
|
||||
column = "client_last_connected";
|
||||
break;
|
||||
|
||||
case property::CLIENT_TOTALCONNECTIONS:
|
||||
column = "client_total_connections";
|
||||
break;
|
||||
|
||||
case property::CLIENT_MONTH_BYTES_UPLOADED:
|
||||
column = "client_month_upload";
|
||||
break;
|
||||
|
||||
case property::CLIENT_TOTAL_BYTES_UPLOADED:
|
||||
column = "client_total_upload";
|
||||
break;
|
||||
|
||||
case property::CLIENT_MONTH_BYTES_DOWNLOADED:
|
||||
column = "client_month_download";
|
||||
break;
|
||||
|
||||
case property::CLIENT_TOTAL_BYTES_DOWNLOADED:
|
||||
column = "client_total_download";
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '{}' for {} (New value: {], Column: {})",
|
||||
prop.type().name,
|
||||
cldbid,
|
||||
prop.value(),
|
||||
column
|
||||
);
|
||||
sql::command(this->sql, "UPDATE `clients_server` SET `" + column + "` = :value WHERE `server_id` = :server_id AND `client_database_id` = :cldbid",
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":cldbid", cldbid},
|
||||
variable{":value", prop.value()}
|
||||
).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
});
|
||||
|
||||
return props;
|
||||
|
@ -8,126 +8,126 @@
|
||||
#include <Properties.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
class VirtualServer;
|
||||
class DataClient;
|
||||
namespace ts::server {
|
||||
class VirtualServer;
|
||||
class DataClient;
|
||||
|
||||
struct ClientDatabaseInfo {
|
||||
ServerId sid;
|
||||
ClientDbId cldbid;
|
||||
std::chrono::time_point<std::chrono::system_clock> created;
|
||||
std::chrono::time_point<std::chrono::system_clock> lastjoin;
|
||||
std::string uniqueId;
|
||||
std::string lastName;
|
||||
uint32_t connections;
|
||||
};
|
||||
struct ClientDatabaseInfo {
|
||||
ServerId server_id;
|
||||
|
||||
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;
|
||||
};
|
||||
ClientDbId client_database_id;
|
||||
std::string client_unique_id;
|
||||
std::string client_nickname;
|
||||
|
||||
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;
|
||||
};
|
||||
std::chrono::time_point<std::chrono::system_clock> client_created;
|
||||
std::chrono::time_point<std::chrono::system_clock> client_last_connected;
|
||||
|
||||
/*
|
||||
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 {
|
||||
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
|
||||
uint64_t id = 0;
|
||||
ChannelId channelId = 0;
|
||||
std::shared_ptr<permission::PermissionTypeEntry> permission = permission::PermissionTypeEntry::unknown;
|
||||
permission::PermissionValue value = 0;
|
||||
permission::PermissionValue grant = 0;
|
||||
uint32_t client_total_connections;
|
||||
};
|
||||
|
||||
bool flag_skip = false;
|
||||
bool flag_negate = false;
|
||||
};
|
||||
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 StartupPropertyEntry {
|
||||
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
|
||||
uint64_t id{0};
|
||||
const property::PropertyDescription* info{&property::undefined_property_description};
|
||||
std::string value;
|
||||
};
|
||||
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;
|
||||
};
|
||||
|
||||
struct StartupCacheEntry {
|
||||
ServerId sid;
|
||||
/*
|
||||
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 {
|
||||
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
|
||||
uint64_t id = 0;
|
||||
ChannelId channelId = 0;
|
||||
std::shared_ptr<permission::PermissionTypeEntry> permission = permission::PermissionTypeEntry::unknown;
|
||||
permission::PermissionValue value = 0;
|
||||
permission::PermissionValue grant = 0;
|
||||
|
||||
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions;
|
||||
std::deque<std::unique_ptr<StartupPropertyEntry>> properties;
|
||||
};
|
||||
bool flag_skip = false;
|
||||
bool flag_negate = false;
|
||||
};
|
||||
|
||||
struct FastPropertyEntry {
|
||||
const property::PropertyDescription* type;
|
||||
std::string value;
|
||||
};
|
||||
struct StartupPropertyEntry {
|
||||
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
|
||||
uint64_t id{0};
|
||||
const property::PropertyDescription* info{&property::undefined_property_description};
|
||||
std::string value;
|
||||
};
|
||||
|
||||
class DatabaseHelper {
|
||||
public:
|
||||
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
|
||||
static bool assignDatabaseId(sql::SqlManager *, ServerId id, std::shared_ptr<DataClient>);
|
||||
static std::mutex database_id_mutex;
|
||||
struct StartupCacheEntry {
|
||||
ServerId sid;
|
||||
|
||||
explicit DatabaseHelper(sql::SqlManager*);
|
||||
~DatabaseHelper();
|
||||
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions;
|
||||
std::deque<std::unique_ptr<StartupPropertyEntry>> properties;
|
||||
};
|
||||
|
||||
void loadStartupCache();
|
||||
size_t cacheBinarySize();
|
||||
void clearStartupCache(ServerId sid = 0);
|
||||
struct FastPropertyEntry {
|
||||
const property::PropertyDescription* type;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
void deleteClient(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>> queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &, std::deque<std::string>);
|
||||
class DatabaseHelper {
|
||||
public:
|
||||
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
|
||||
static bool assignDatabaseId(sql::SqlManager *, ServerId serverId, std::shared_ptr<DataClient>);
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
explicit DatabaseHelper(sql::SqlManager*);
|
||||
~DatabaseHelper();
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
void loadStartupCache();
|
||||
size_t cacheBinarySize();
|
||||
void clearStartupCache(ServerId sid = 0);
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
|
||||
void saveGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
void deleteClient(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>> queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &, std::deque<std::string>);
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
std::shared_ptr<Properties> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
|
||||
std::shared_ptr<Properties> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
|
||||
std::shared_ptr<Properties> loadChannelProperties(const std::shared_ptr<VirtualServer>&, ChannelId); //Read and write
|
||||
std::shared_ptr<Properties> loadClientProperties(const std::shared_ptr<VirtualServer>&, ClientDbId, ClientType);
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
bool deleteGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
|
||||
bool deleteChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
bool deletePlaylist(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
std::deque<std::unique_ptr<FastPropertyEntry>> query_properties(ServerId /* server */, property::PropertyType /* type */, uint64_t /* id */); /* required for server snapshots */
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
|
||||
void saveGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
void tick();
|
||||
private:
|
||||
void loadStartupPermissionCache();
|
||||
void loadStartupPropertyCache();
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
bool use_startup_cache = false;
|
||||
threads::Mutex startup_lock;
|
||||
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
|
||||
std::shared_ptr<Properties> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
|
||||
std::shared_ptr<Properties> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
|
||||
std::shared_ptr<Properties> loadChannelProperties(const std::shared_ptr<VirtualServer>&, ChannelId); //Read and write
|
||||
std::shared_ptr<Properties> loadClientProperties(const std::shared_ptr<VirtualServer>&, ClientDbId, ClientType);
|
||||
|
||||
sql::SqlManager* sql = nullptr;
|
||||
threads::Mutex permManagerLock;
|
||||
std::deque<CachedPermissionManager*> cachedPermissionManagers;
|
||||
threads::Mutex propsLock;
|
||||
std::deque<CachedProperties*> cachedProperties;
|
||||
};
|
||||
}
|
||||
bool deleteGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
|
||||
bool deleteChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
bool deletePlaylist(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
std::deque<std::unique_ptr<FastPropertyEntry>> query_properties(ServerId /* server */, property::PropertyType /* type */, uint64_t /* id */); /* required for server snapshots */
|
||||
|
||||
void tick();
|
||||
private:
|
||||
void loadStartupPermissionCache();
|
||||
void loadStartupPropertyCache();
|
||||
|
||||
bool use_startup_cache = false;
|
||||
threads::Mutex startup_lock;
|
||||
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
|
||||
|
||||
sql::SqlManager* sql = nullptr;
|
||||
threads::Mutex permManagerLock;
|
||||
std::deque<CachedPermissionManager*> cachedPermissionManagers;
|
||||
threads::Mutex propsLock;
|
||||
std::deque<CachedProperties*> cachedProperties;
|
||||
};
|
||||
}
|
@ -256,6 +256,7 @@ uint16_t VirtualServerManager::next_available_port(const std::string& host_strin
|
||||
|
||||
ts::ServerId VirtualServerManager::next_available_server_id(bool& success) {
|
||||
auto server_id_base = this->handle->properties()[property::SERVERINSTANCE_VIRTUAL_SERVER_ID_INDEX].as<ServerId>();
|
||||
/* ensure we're not using 0xFFFF (This is the snapshot server) */
|
||||
if(server_id_base > 65530) {
|
||||
success = false;
|
||||
return 0;
|
||||
@ -340,7 +341,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
|
||||
if(!sid_success)
|
||||
return nullptr;
|
||||
|
||||
this->delete_server_in_db(serverId); /* just to ensure */
|
||||
this->delete_server_in_db(serverId, true); /* 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"});
|
||||
|
||||
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;",
|
||||
@ -418,7 +419,7 @@ 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->delete_server_in_db(server->serverId);
|
||||
this->delete_server_in_db(server->serverId, true);
|
||||
|
||||
if(auto file_vs = file::server()->find_virtual_server(server->getServerId()); file_vs) {
|
||||
using ErrorType = file::filesystem::ServerCommandErrorType;
|
||||
@ -480,7 +481,7 @@ void VirtualServerManager::tickHandshakeClients() {
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualServerManager::delete_server_in_db(ts::ServerId server_id) {
|
||||
void VirtualServerManager::delete_server_in_db(ts::ServerId server_id, bool data_only) {
|
||||
#define execute_delete(statement) \
|
||||
result = sql::command(this->handle->getSql(), statement, variable{":sid", server_id}).execute(); \
|
||||
if(!result) { \
|
||||
@ -490,21 +491,60 @@ if(!result) { \
|
||||
|
||||
sql::result result{};
|
||||
|
||||
if(!data_only) {
|
||||
execute_delete("DELETE FROM `servers` WHERE `serverId` = :sid");
|
||||
}
|
||||
|
||||
execute_delete("DELETE FROM `tokens` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `properties` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `permissions` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `clients` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `channels` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `bannedClients` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `ban_trigger` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `groups` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `assignedGroups` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `servers` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `complains` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `letters` WHERE `serverId` = :sid");
|
||||
|
||||
execute_delete("DELETE FROM `musicbots` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `conversations` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `conversation_blocks` WHERE `server_id` = :sid");
|
||||
|
||||
execute_delete("DELETE FROM `playlists` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `playlist_songs` WHERE `serverId` = :sid");
|
||||
|
||||
execute_delete("DELETE FROM `conversations` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `conversation_blocks` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `ban_trigger` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `clients_server` WHERE `server_id` = :sid");
|
||||
}
|
||||
|
||||
#define execute_change(table, column) \
|
||||
result = sql::command(this->handle->getSql(), "UPDATE TABLE `" table "` SET `" column "` = :nsid WHERE `" column "` = :osid;", \
|
||||
variable{":osid", old_id}, variable{":nsid", new_id}).execute(); \
|
||||
if(!result) { \
|
||||
logWarning(LOG_INSTANCE, "Failed to execute server id change on table {} (column {}): {}", table, column, result.fmtStr()); \
|
||||
result = sql::result{}; \
|
||||
}
|
||||
|
||||
void VirtualServerManager::change_server_id_in_db(ts::ServerId old_id, ts::ServerId new_id) {
|
||||
sql::result result{};
|
||||
|
||||
|
||||
execute_change("tokens", "serverId");
|
||||
execute_change("properties", "serverId");
|
||||
execute_change("permissions", "serverId");
|
||||
execute_change("channels", "serverId");
|
||||
execute_change("bannedClients", "serverId");
|
||||
execute_change("groups", "serverId");
|
||||
execute_change("assignedGroups", "serverId");
|
||||
execute_change("complains", "serverId");
|
||||
execute_change("letters", "serverId");
|
||||
execute_change("musicbots", "serverId");
|
||||
execute_change("playlists", "serverId");
|
||||
execute_change("playlist_songs", "serverId");
|
||||
|
||||
execute_change("conversations", "server_id");
|
||||
execute_change("conversation_blocks", "server_id");
|
||||
execute_change("ban_trigger", "server_id");
|
||||
execute_change("clients_server", "server_id");
|
||||
|
||||
execute_change("servers", "serverId");
|
||||
}
|
@ -28,6 +28,16 @@ namespace ts::server {
|
||||
STOPPING
|
||||
};
|
||||
|
||||
enum struct SnapshotDeployResult {
|
||||
SUCCESS,
|
||||
|
||||
REACHED_SOFTWARE_SERVER_LIMIT,
|
||||
REACHED_CONFIG_SERVER_LIMIT,
|
||||
REACHED_SERVER_ID_LIMIT,
|
||||
|
||||
CUSTOM_ERROR /* error message set */
|
||||
};
|
||||
|
||||
explicit VirtualServerManager(InstanceHandler*);
|
||||
~VirtualServerManager();
|
||||
|
||||
@ -57,7 +67,9 @@ namespace ts::server {
|
||||
//Dotn 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);
|
||||
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
|
||||
bool deploy_snapshot(std::string& /* error */, ServerId /* target server id */, 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; }
|
||||
|
||||
@ -74,6 +86,7 @@ namespace ts::server {
|
||||
}
|
||||
io::VoiceIOManager* ioManager(){ return this->_ioManager; }
|
||||
|
||||
/* This must be recursive */
|
||||
threads::Mutex server_create_lock;
|
||||
|
||||
State getState() { return this->state; }
|
||||
@ -97,12 +110,9 @@ namespace ts::server {
|
||||
|
||||
void tickHandshakeClients();
|
||||
|
||||
void delete_server_in_db(ServerId /* server id */);
|
||||
void delete_server_in_db(ServerId /* server id */, bool /* data only */);
|
||||
void change_server_id_in_db(ServerId /* old id */, ServerId /* new id */);
|
||||
|
||||
/* methods used to preprocess a snapshot */
|
||||
bool deploy_ts3_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||
bool deploy_teaspeak_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||
/* actual deploy method */
|
||||
bool deploy_raw_snapshot(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 */);
|
||||
bool try_deploy_snapshot(std::string& /* error */, ServerId /* target server id */, ServerId /* logging server id */, const command_parser& /* source */);
|
||||
};
|
||||
}
|
@ -391,8 +391,8 @@ bool ConnectedClient::handle_text_command(
|
||||
for(const auto& song : bot_playlist->list_songs()) {
|
||||
string invoker = "unknown";
|
||||
for(const auto& e : dbinfo) {
|
||||
if(e->cldbid == song->invoker) {
|
||||
invoker = "[URL=client://0/" + e->uniqueId + "~" + e->lastName + "]" + e->lastName + "[/URL]";
|
||||
if(e->client_database_id == song->invoker) {
|
||||
invoker = "[URL=client://0/" + e->client_unique_id + "~" + e->client_nickname + "]" + e->client_nickname + "[/URL]";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
#include <log/LogUtils.h>
|
||||
#include <src/client/voice/VoiceClient.h>
|
||||
#include <misc/sassert.h>
|
||||
#include <misc/hex.h>
|
||||
#include "DataClient.h"
|
||||
#include "ConnectedClient.h"
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "misc/base64.h"
|
||||
#include <misc/base64.h>
|
||||
|
||||
#include "./DataClient.h"
|
||||
#include "../InstanceHandler.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ts;
|
||||
@ -22,52 +21,78 @@ DataClient::~DataClient() {
|
||||
this->_properties = nullptr;
|
||||
}
|
||||
|
||||
bool DataClient::loadDataForCurrentServer() { //TODO for query
|
||||
if(this->getUid().empty()) return false;
|
||||
constexpr static std::string_view kClientLoadCommand{R"(
|
||||
SELECT
|
||||
`client_database_id`,
|
||||
`client_created`,
|
||||
|
||||
`client_last_connected`,
|
||||
`client_total_connections`,
|
||||
|
||||
`client_month_upload`,
|
||||
`client_month_download`,
|
||||
|
||||
`client_total_upload`,
|
||||
`client_total_download`
|
||||
FROM `clients_server` WHERE `server_id` = :sid AND `client_unique_id` = :uid
|
||||
)"};
|
||||
|
||||
bool DataClient::loadDataForCurrentServer() {
|
||||
auto uniqueId = this->getUid();
|
||||
if(uniqueId.empty())
|
||||
return false;
|
||||
|
||||
auto ref_server = this->server;
|
||||
auto server_id = ref_server ? ref_server->getServerId() : 0;
|
||||
|
||||
properties()[property::CLIENT_DATABASE_ID] = 0;
|
||||
properties()[property::CLIENT_CREATED] = 0;
|
||||
properties()[property::CLIENT_TOTALCONNECTIONS] = 0;
|
||||
this->clientPermissions = std::make_shared<permission::v2::PermissionManager>();
|
||||
|
||||
auto properties = this->properties();
|
||||
sql::command{this->sql, std::string{kClientLoadCommand}, variable{":uid", uniqueId}, variable{":sid", server_id}}.query([&](int length, std::string* values, std::string* names) {
|
||||
auto index{0};
|
||||
|
||||
ClientDbId client_db_id = 0;
|
||||
sql::command(this->sql, "SELECT `cldbid`,`firstConnect`,`connections` FROM `clients` WHERE `serverId` = :sid AND `clientUid` = :uid LIMIT 1",
|
||||
variable{":sid", server_id},
|
||||
variable{":uid", this->getUid()}
|
||||
).query([&](DataClient* cl, int length, string* values, string* names){
|
||||
for (int index = 0; index < length; index++) {
|
||||
try {
|
||||
if (names[index] == "cldbid") {
|
||||
client_db_id = stoull(values[index]);
|
||||
} else if (names[index] == "firstConnect") {
|
||||
cl->properties()[property::CLIENT_CREATED] = values[index];
|
||||
} else if (names[index] == "connections") {
|
||||
cl->properties()[property::CLIENT_TOTALCONNECTIONS] = values[index];
|
||||
} else {
|
||||
logWarning(LOG_INSTANCE, "Received unknown column with name {} within client list", names[index]);
|
||||
}
|
||||
} catch(const std::exception& ex) {
|
||||
logError(server_id, "Failed to load client {} base properties from database. Colum parsing for column {} failed. Value: {}. Message: {}",
|
||||
this->getUid(),
|
||||
names[index],
|
||||
values[index],
|
||||
ex.what()
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
assert(names[index] == "client_database_id");
|
||||
properties[property::CLIENT_DATABASE_ID] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_created");
|
||||
properties[property::CLIENT_CREATED] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_last_connected");
|
||||
properties[property::CLIENT_LASTCONNECTED] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
properties[property::CLIENT_TOTALCONNECTIONS] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_month_upload");
|
||||
properties[property::CLIENT_MONTH_BYTES_UPLOADED] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_month_download");
|
||||
properties[property::CLIENT_MONTH_BYTES_DOWNLOADED] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_total_upload");
|
||||
properties[property::CLIENT_TOTAL_BYTES_UPLOADED] = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_total_download");
|
||||
properties[property::CLIENT_TOTAL_BYTES_DOWNLOADED] = std::stoull(values[index++]);
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
logError(server_id, "Failed to load client {} base properties from database: {} (Index {})",
|
||||
this->getUid(),
|
||||
ex.what(),
|
||||
index - 1
|
||||
);
|
||||
properties[property::CLIENT_DATABASE_ID] = 0;
|
||||
}
|
||||
return 0;
|
||||
}, this);
|
||||
});
|
||||
|
||||
if(client_db_id == 0)
|
||||
if(this->properties()[property::CLIENT_DATABASE_ID].as<ClientDbId>() == 0)
|
||||
return false;
|
||||
|
||||
this->properties()[property::CLIENT_DATABASE_ID] = client_db_id; /* do this before the property saving (it saved the cldbid as well!)*/
|
||||
|
||||
//Load general properties
|
||||
deque<ts::PropertyWrapper> copied;
|
||||
std::deque<ts::PropertyWrapper> copied;
|
||||
for(const auto& prop : this->_properties->list_properties()){
|
||||
if((prop.type().flags & property::FLAG_GLOBAL) == 0) continue;
|
||||
if(prop.type().default_value == prop.value()) continue;
|
||||
@ -80,28 +105,7 @@ bool DataClient::loadDataForCurrentServer() { //TODO for query
|
||||
return false;
|
||||
}
|
||||
|
||||
auto client_type = this->getType();
|
||||
if(client_type == CLIENT_TEAMSPEAK || ref_server) {
|
||||
this->_properties = serverInstance->databaseHelper()->loadClientProperties(ref_server, this->getClientDatabaseId(), client_type);
|
||||
} else {
|
||||
this->_properties = DatabaseHelper::default_properties_client(nullptr, client_type);
|
||||
|
||||
this->_properties->registerNotifyHandler([&, server_id, client_db_id](Property& prop){
|
||||
std::string query;
|
||||
if(prop.type() == property::CLIENT_TOTALCONNECTIONS)
|
||||
query = "UPDATE `clients` SET `connections` = :value WHERE `serverId` = :sid AND `cldbid` = :cldbid";
|
||||
else if(prop.type() == property::CLIENT_NICKNAME)
|
||||
query = "UPDATE `clients` SET `lastName` = :value WHERE `serverId` = :sid AND `cldbid` = :cldbid";
|
||||
else if(prop.type() == property::CLIENT_LASTCONNECTED)
|
||||
query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :sid AND `cldbid` = :cldbid";
|
||||
else
|
||||
return;
|
||||
|
||||
debugMessage(server_id, "[Property] Updating general client table property for client {}. Key: {} Value: {}", client_db_id, prop.type().name, prop.value());
|
||||
sql::command(this->sql, query, variable{":sid", 0}, variable{":cldbid", client_db_id}, variable{":value", prop.value()}).executeLater()
|
||||
.waitAndGetLater(LOG_SQL_CMD, {1, "failed to update general client properties"});
|
||||
});
|
||||
}
|
||||
this->_properties = serverInstance->databaseHelper()->loadClientProperties(ref_server, this->getClientDatabaseId(), this->getType());
|
||||
|
||||
this->_properties->toggleSave(false);
|
||||
for(const auto& e : copied) {
|
||||
|
@ -367,18 +367,22 @@ command_result ConnectedClient::handleCommandClientChatClosed(Command &cmd) {
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
//start=0 duration=10
|
||||
//pattern=%asd%
|
||||
|
||||
struct ClientDbArgs {
|
||||
shared_ptr<VirtualServer> server;
|
||||
int index = 0;
|
||||
int offset = 0;
|
||||
int resultIndex = 0;
|
||||
bool showIp = false;
|
||||
bool largeInfo = false;
|
||||
Command *result = nullptr;
|
||||
};
|
||||
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) {
|
||||
CMD_REQ_SERVER;
|
||||
@ -386,86 +390,79 @@ command_result ConnectedClient::handleCommandClientDbList(Command &cmd) {
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(25);
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dblist, 1);
|
||||
|
||||
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdblist" : "");
|
||||
size_t offset = cmd[0].has("start") ? cmd["start"].as<size_t>() : 0;
|
||||
size_t limit = cmd[0].has("duration") ? cmd["duration"].as<int>() : 0;
|
||||
if(limit > 2000 || limit < 1)
|
||||
limit = 2000;
|
||||
|
||||
if (!cmd[0].has("start"))
|
||||
cmd["start"] = 0;
|
||||
if (!cmd[0].has("duration"))
|
||||
cmd["duration"] = 20;
|
||||
if (cmd[0]["duration"].as<int>() > 2000) cmd["duration"] = 2000;
|
||||
if (cmd[0]["duration"].as<int>() < 1) cmd["duration"] = 1;
|
||||
ts::command_builder result{this->notify_response_command("notifyclientdblist")};
|
||||
result.reserve_bulks(limit);
|
||||
|
||||
auto maxIndex = cmd["start"].as<uint32_t>() + cmd["duration"].as<uint32_t>();
|
||||
ClientDbArgs args;
|
||||
args.server = this->server;
|
||||
args.offset = cmd["start"].as<uint32_t>();
|
||||
args.result = ¬ify;
|
||||
args.resultIndex = 0;
|
||||
args.index = 0;
|
||||
args.showIp = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||
args.largeInfo = cmd.hasParm("details");
|
||||
size_t command_index{0}, set_index{0};
|
||||
|
||||
(LOG_SQL_CMD)(sql::command(this->server->getSql(), "SELECT * FROM `clients` WHERE `serverId` = :sid ORDER BY `cldbid` ASC" + (maxIndex > 0 ? " LIMIT " + to_string(maxIndex) : ""), variable{":sid", this->server->getServerId()}).query(
|
||||
[](ClientDbArgs *pArgs, int length, char **values, char **column) {
|
||||
pArgs->index++;
|
||||
if (pArgs->offset < pArgs->index) {
|
||||
ClientDbId id = 0;
|
||||
string uid, name, ip;
|
||||
string created = "0", lastConnected = "0", connections = "0";
|
||||
for (int index = 0; index < length; index++) {
|
||||
string key = column[index];
|
||||
if (key == "cldbid")
|
||||
id = stoll(values[index]);
|
||||
else if (key == "clientUid")
|
||||
uid = values[index];
|
||||
else if (key == "firstConnect")
|
||||
created = values[index];
|
||||
else if (key == "lastConnect")
|
||||
lastConnected = values[index];
|
||||
else if (key == "connections")
|
||||
connections = values[index];
|
||||
else if (key == "lastName")
|
||||
name = values[index];
|
||||
}
|
||||
auto show_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||
|
||||
pArgs->result->operator[](pArgs->resultIndex)["cldbid"] = id;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_unique_identifier"] = uid;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_nickname"] = name;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_created"] = created;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_lastconnected"] = lastConnected;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_totalconnections"] = connections;
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_description"] = "";
|
||||
auto sqlResult = sql::command{this->server->getSql(), kDBListQuery, variable{":serverId", this->getServerId()}, variable{":offset", offset}, variable{":limit", limit}}
|
||||
.query([&](int length, std::string* values, std::string* names) {
|
||||
set_index++;
|
||||
auto bulk = result.bulk(command_index++);
|
||||
bulk.reserve(300);
|
||||
|
||||
auto props = serverInstance->databaseHelper()->loadClientProperties(pArgs->server, id, ClientType::CLIENT_TEAMSPEAK);
|
||||
if (props) {
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as<string>();
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as<string>();
|
||||
auto index{0};
|
||||
try {
|
||||
assert(names[index] == "client_database_id");
|
||||
bulk.put_unchecked("cldbid", values[index++]);
|
||||
|
||||
if (pArgs->largeInfo) {
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_badges"] = (*props)[property::CLIENT_BADGES].as<string>();
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_version"] = (*props)[property::CLIENT_VERSION].as<string>();
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_platform"] = (*props)[property::CLIENT_PLATFORM].as<string>();
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as<string>();
|
||||
}
|
||||
}
|
||||
if (!pArgs->showIp)
|
||||
pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = "hidden";
|
||||
pArgs->resultIndex++;
|
||||
}
|
||||
return 0;
|
||||
}, &args));
|
||||
assert(names[index] == "client_unique_id");
|
||||
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};
|
||||
|
||||
if (args.resultIndex == 0) return command_result{error::database_empty_result};
|
||||
if (cmd.hasParm("count")) {
|
||||
size_t result = 0;
|
||||
sql::command(this->server->getSql(), "SELECT COUNT(*) AS `count` FROM `clients` WHERE `serverId` = :sid", variable{":sid", this->server->getServerId()}).query([](size_t *ptr, int, char **v, char **) {
|
||||
*ptr = static_cast<size_t>(stoll(v[0]));
|
||||
return 0;
|
||||
}, &result);
|
||||
notify[0]["count"] = result;
|
||||
size_t count{0};
|
||||
sql::command(this->server->getSql(), "SELECT COUNT(`client_database_id`) AS `count` FROM `clients_server` WHERE `server_id` = :sid", variable{":sid", this->server->getServerId()})
|
||||
.query([&](int, std::string* v, std::string*) {
|
||||
count = stoll(v[0]);
|
||||
});
|
||||
result.put_unchecked(0, "count", count);
|
||||
}
|
||||
this->sendCommand(notify);
|
||||
|
||||
this->sendCommand(result);
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@ -869,8 +866,9 @@ command_result ConnectedClient::handleCommandClientGetDBIDfromUID(Command &cmd)
|
||||
CMD_RESET_IDLE;
|
||||
|
||||
deque<string> unique_ids;
|
||||
for(int index = 0; index < cmd.bulkCount(); index++)
|
||||
unique_ids.push_back(cmd[index]["cluid"].as<string>());
|
||||
for(int index = 0; index < cmd.bulkCount(); index++) {
|
||||
unique_ids.push_back(cmd[index]["cluid"]);
|
||||
}
|
||||
|
||||
auto res = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->server, unique_ids);
|
||||
if (res.empty()) return command_result{error::database_empty_result};
|
||||
@ -878,8 +876,8 @@ command_result ConnectedClient::handleCommandClientGetDBIDfromUID(Command &cmd)
|
||||
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdbidfromuid" : "");
|
||||
int result_index = 0;
|
||||
for(auto& info : res) {
|
||||
result[result_index]["cluid"] = info->uniqueId;
|
||||
result[result_index]["cldbid"] = info->cldbid;
|
||||
result[result_index]["cluid"] = info->client_unique_id;
|
||||
result[result_index]["cldbid"] = info->client_database_id;
|
||||
result_index++;
|
||||
}
|
||||
this->sendCommand(result);
|
||||
@ -900,10 +898,10 @@ command_result ConnectedClient::handleCommandClientGetNameFromDBID(Command &cmd)
|
||||
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetnamefromdbid" : "");
|
||||
int result_index = 0;
|
||||
for(auto& info : res) {
|
||||
result[result_index]["cluid"] = info->uniqueId;
|
||||
result[result_index]["cldbid"] = info->cldbid;
|
||||
result[result_index]["name"] = info->lastName;
|
||||
result[result_index]["clname"] = info->lastName;
|
||||
result[result_index]["cluid"] = info->client_unique_id;
|
||||
result[result_index]["cldbid"] = info->client_database_id;
|
||||
result[result_index]["name"] = info->client_nickname;
|
||||
result[result_index]["clname"] = info->client_nickname;
|
||||
result_index++;
|
||||
}
|
||||
this->sendCommand(result);
|
||||
@ -924,10 +922,10 @@ command_result ConnectedClient::handleCommandClientGetNameFromUid(Command &cmd)
|
||||
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientnamefromuid" : "");
|
||||
int result_index = 0;
|
||||
for(auto& info : res) {
|
||||
result[result_index]["cluid"] = info->uniqueId;
|
||||
result[result_index]["cldbid"] = info->cldbid;
|
||||
result[result_index]["name"] = info->lastName;
|
||||
result[result_index]["clname"] = info->lastName;
|
||||
result[result_index]["cluid"] = info->client_unique_id;
|
||||
result[result_index]["cldbid"] = info->client_database_id;
|
||||
result[result_index]["name"] = info->client_nickname;
|
||||
result[result_index]["clname"] = info->client_nickname;
|
||||
result_index++;
|
||||
}
|
||||
this->sendCommand(result);
|
||||
@ -1086,16 +1084,16 @@ command_result ConnectedClient::handleCommandClientDbInfo(Command &cmd) {
|
||||
|
||||
size_t index = 0;
|
||||
for(const auto& info : basic) {
|
||||
res[index]["client_base64HashClientUID"] = hex::hex(base64::validate(info->uniqueId) ? base64::decode(info->uniqueId) : info->uniqueId, 'a', 'q');
|
||||
res[index]["client_unique_identifier"] = info->uniqueId;
|
||||
res[index]["client_nickname"] = info->lastName;
|
||||
res[index]["client_database_id"] = info->cldbid;
|
||||
res[index]["client_created"] = chrono::duration_cast<chrono::seconds>(info->created.time_since_epoch()).count();
|
||||
res[index]["client_lastconnected"] = chrono::duration_cast<chrono::seconds>(info->lastjoin.time_since_epoch()).count();
|
||||
res[index]["client_totalconnections"] = info->connections;
|
||||
res[index]["client_database_id"] = info->cldbid;
|
||||
res[index]["client_base64HashClientUID"] = hex::hex(base64::validate(info->client_unique_id) ? base64::decode(info->client_unique_id) : info->client_unique_id, 'a', 'q');
|
||||
res[index]["client_unique_identifier"] = info->client_unique_id;
|
||||
res[index]["client_nickname"] = info->client_nickname;
|
||||
res[index]["client_database_id"] = info->client_database_id;
|
||||
res[index]["client_created"] = chrono::duration_cast<chrono::seconds>(info->client_created.time_since_epoch()).count();
|
||||
res[index]["client_lastconnected"] = chrono::duration_cast<chrono::seconds>(info->client_last_connected.time_since_epoch()).count();
|
||||
res[index]["client_totalconnections"] = info->client_total_connections;
|
||||
res[index]["client_database_id"] = info->client_database_id;
|
||||
|
||||
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, info->cldbid, ClientType::CLIENT_TEAMSPEAK);
|
||||
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, info->client_database_id, ClientType::CLIENT_TEAMSPEAK);
|
||||
if (allow_ip)
|
||||
res[index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as<string>();
|
||||
else
|
||||
@ -1136,13 +1134,6 @@ command_result ConnectedClient::handleCommandClientDBDelete(Command &cmd) {
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
struct DBFindArgs {
|
||||
int index = 0;
|
||||
bool full = false;
|
||||
bool ip = false;
|
||||
Command cmd{""};
|
||||
};
|
||||
|
||||
command_result ConnectedClient::handleCommandClientDBFind(Command &cmd) {
|
||||
CMD_REQ_SERVER;
|
||||
CMD_RESET_IDLE;
|
||||
@ -1152,48 +1143,73 @@ command_result ConnectedClient::handleCommandClientDBFind(Command &cmd) {
|
||||
bool uid = cmd.hasParm("uid");
|
||||
string pattern = cmd["pattern"];
|
||||
|
||||
DBFindArgs args{};
|
||||
args.cmd = Command(this->getType() == CLIENT_QUERY ? "" : "notifyclientdbfind");
|
||||
args.full = cmd.hasParm("details");
|
||||
args.ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||
auto res = sql::command(this->sql, "SELECT * FROM `clients` WHERE `serverId` = :sid AND `" + std::string{uid ? "clientUid" : "lastName"} + "` LIKE :pattern LIMIT 50",
|
||||
variable{":sid", this->server->getServerId()},
|
||||
variable{":pattern", pattern}).query(
|
||||
[&](DBFindArgs *ptr, int len, char **values, char **names) {
|
||||
for (int index = 0; index < len; index++)
|
||||
if (strcmp(names[index], "cldbid") == 0)
|
||||
ptr->cmd[ptr->index]["cldbid"] = values[index];
|
||||
else if (strcmp(names[index], "clientUid") == 0 && ptr->full)
|
||||
ptr->cmd[ptr->index]["client_unique_identifier"] = values[index];
|
||||
else if (strcmp(names[index], "lastConnect") == 0 && ptr->full)
|
||||
ptr->cmd[ptr->index]["client_lastconnected"] = values[index];
|
||||
else if (strcmp(names[index], "connections") == 0 && ptr->full)
|
||||
ptr->cmd[ptr->index]["client_totalconnections"] = values[index];
|
||||
else if (strcmp(names[index], "lastName") == 0 && ptr->full)
|
||||
ptr->cmd[ptr->index]["client_nickname"] = values[index];
|
||||
if (ptr->full) {
|
||||
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, ptr->cmd[ptr->index]["cldbid"], ClientType::CLIENT_TEAMSPEAK);
|
||||
if (props) {
|
||||
if (ptr->ip) {
|
||||
ptr->cmd[ptr->index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as<string>();
|
||||
} else {
|
||||
ptr->cmd[ptr->index]["client_lastip"] = "hidden";
|
||||
}
|
||||
ptr->cmd[ptr->index]["client_badges"] = (*props)[property::CLIENT_BADGES].as<string>();
|
||||
ptr->cmd[ptr->index]["client_version"] = (*props)[property::CLIENT_VERSION].as<string>();
|
||||
ptr->cmd[ptr->index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as<string>();
|
||||
ptr->cmd[ptr->index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as<string>();
|
||||
}
|
||||
const auto detailed = cmd.hasParm("details");
|
||||
const auto show_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||
|
||||
size_t command_index{0};
|
||||
ts::command_builder result{this->notify_response_command("notifyclientdbfind")};
|
||||
result.reserve_bulks(50);
|
||||
|
||||
constexpr static auto kBaseCommand{"SELECT `client_database_id`, `client_unique_id`, `client_nickname`, `client_ip`, `client_last_connected`, `client_total_connections` FROM `clients_server` WHERE "};
|
||||
|
||||
auto sql_result = sql::command{this->sql, std::string{kBaseCommand} + "`server_id` = :sid AND " + (uid ? "`client_unique_id`" : "`client_nickname`") + " LIKE :pattern LIMIT 50", variable{":sid", this->getServerId()}, variable{":pattern", pattern}}
|
||||
.query([&](int length, std::string* values, std::string* names) {
|
||||
auto bulk = result.bulk(command_index++);
|
||||
bulk.reserve(300);
|
||||
|
||||
auto index{0};
|
||||
ClientDbId client_database_id;
|
||||
try {
|
||||
assert(names[index] == "client_database_id");
|
||||
client_database_id = std::stoull(values[index]);
|
||||
bulk.put_unchecked("cldbid", values[index++]);
|
||||
|
||||
assert(names[index] == "client_unique_id");
|
||||
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");
|
||||
if(detailed) {
|
||||
bulk.put_unchecked("client_lastip", show_ip ? values[index++] : "hidden");
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
ptr->index++;
|
||||
return 0;
|
||||
}, &args);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(res);
|
||||
if (args.index == 0) return command_result{error::database_empty_result};
|
||||
|
||||
this->sendCommand(args.cmd);
|
||||
assert(names[index] == "client_last_connected");
|
||||
bulk.put_unchecked("client_lastconnected", values[index++]);
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
bulk.put_unchecked("client_totalconnections", values[index++]);
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
command_index--;
|
||||
logError(this->getServerId(), "Failed to parse client base properties at index {}: {}. Search pattern: {}",
|
||||
index - 1,
|
||||
ex.what(),
|
||||
pattern
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if(detailed) {
|
||||
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, client_database_id, ClientType::CLIENT_TEAMSPEAK);
|
||||
if (props) {
|
||||
auto& properties = *props;
|
||||
bulk.put_unchecked("client_badges", properties[property::CLIENT_BADGES].as<std::string>());
|
||||
bulk.put_unchecked("client_version", properties[property::CLIENT_VERSION].as<std::string>());
|
||||
bulk.put_unchecked("client_platform", properties[property::CLIENT_PLATFORM].as<std::string>());
|
||||
bulk.put_unchecked("client_hwid", properties[property::CLIENT_HARDWARE_ID].as<std::string>());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(command_index == 0)
|
||||
return command_result{error::database_empty_result};
|
||||
|
||||
this->sendCommand(result);
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
|
@ -714,7 +714,7 @@ command_result ConnectedClient::handleCommandBanAdd(Command &cmd) {
|
||||
return command_result{permission::b_client_ban_create};
|
||||
}
|
||||
|
||||
auto max_ban_time = server->calculate_permission(permission::i_client_ban_max_bantime, this->getClientDatabaseId(), this->getType(), 0);
|
||||
auto max_ban_time = this->calculate_permission(permission::i_client_ban_max_bantime, this->getClientDatabaseId(), this->getType(), 0);
|
||||
if(!max_ban_time.has_value) return command_result{permission::i_client_ban_max_bantime};
|
||||
if (!max_ban_time.has_infinite_power()) {
|
||||
if (max_ban_time.value < time)
|
||||
@ -1884,10 +1884,10 @@ command_result ConnectedClient::handleCommandComplainList(Command &cmd) {
|
||||
result[index]["timestamp"] = chrono::duration_cast<chrono::seconds>(elm->created.time_since_epoch()).count();
|
||||
|
||||
for (const auto &e : dbInfo) {
|
||||
if (e->cldbid == elm->target)
|
||||
result[index]["tname"] = e->lastName;
|
||||
if (e->cldbid == elm->invoker)
|
||||
result[index]["fname"] = e->lastName;
|
||||
if (e->client_database_id == elm->target)
|
||||
result[index]["tname"] = e->client_nickname;
|
||||
if (e->client_database_id == elm->invoker)
|
||||
result[index]["fname"] = e->client_nickname;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@ -2351,7 +2351,7 @@ command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) {
|
||||
auto info = serverInstance->databaseHelper()->queryDatabaseInfo(server, {cmd["cldbid"].as<ClientDbId>()});
|
||||
if(info.empty())
|
||||
return command_result{error::database_empty_result};
|
||||
uid = info[0]->uniqueId;
|
||||
uid = info[0]->client_unique_id;
|
||||
} else {
|
||||
if(server) {
|
||||
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create_own, this->getClientDatabaseId(), this->getType(), 0)))
|
||||
|
@ -98,7 +98,7 @@ void MusicClient::replay_song(const shared_ptr<music::PlayableSong> &entry, cons
|
||||
auto info_list = serverInstance->databaseHelper()->queryDatabaseInfo(self->getServer(), {entry->getInvoker()});
|
||||
if(!info_list.empty()) {
|
||||
auto info = info_list.front();
|
||||
invoker = "[URL=client://0/" + info->uniqueId + "~WolverinDEV]" + info->lastName + "[/URL]";
|
||||
invoker = "[URL=client://0/" + info->client_unique_id + "~WolverinDEV]" + info->client_nickname + "[/URL]";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ command_result QueryClient::handleCommand(Command& cmd) {
|
||||
case string_hash("bindinglist"):
|
||||
return this->handleCommandBindingList(cmd);
|
||||
case string_hash("serversnapshotdeploy"): {
|
||||
#if 1
|
||||
#if 0
|
||||
return this->handleCommandServerSnapshotDeploy(cmd);
|
||||
#else
|
||||
auto cmd_str = cmd.build();
|
||||
@ -911,33 +911,30 @@ command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::comma
|
||||
if(this->server) {
|
||||
return command_result{error::not_implemented};
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
||||
//host = this->server->properties()[property::VIRTUALSERVER_HOST].as<string>();
|
||||
//port = this->server->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>();
|
||||
} else {
|
||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
||||
}
|
||||
|
||||
std::string error{};
|
||||
unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock);
|
||||
//TODO: Create a server if no exists
|
||||
auto result = serverInstance->getVoiceServerManager()->deploy_snapshot(error, this->server, command);
|
||||
|
||||
{ /* TODO: Only check if we're creating a new server */
|
||||
auto instances = serverInstance->getVoiceServerManager()->serverInstances();
|
||||
if(config::server::max_virtual_server != -1 && instances.size() > config::server::max_virtual_server)
|
||||
using SnapshotDeployResult = VirtualServerManager::SnapshotDeployResult;
|
||||
switch (result) {
|
||||
case SnapshotDeployResult::SUCCESS:
|
||||
break;
|
||||
|
||||
case SnapshotDeployResult::REACHED_SERVER_ID_LIMIT:
|
||||
return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Server ID limit reached)"};
|
||||
|
||||
case SnapshotDeployResult::REACHED_SOFTWARE_SERVER_LIMIT:
|
||||
return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Software limit reached)"};
|
||||
|
||||
case SnapshotDeployResult::REACHED_CONFIG_SERVER_LIMIT:
|
||||
return command_result{error::server_max_vs_reached, "You reached the via config.yml enabled virtual server limit."};
|
||||
|
||||
if(instances.size() >= 65535)
|
||||
return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Software limit reached)"};
|
||||
case SnapshotDeployResult::CUSTOM_ERROR:
|
||||
return command_result{error::vs_critical, error};
|
||||
}
|
||||
|
||||
server_create_lock.unlock();
|
||||
|
||||
//TODO: Stop the server completely
|
||||
if(!serverInstance->getVoiceServerManager()->deploy_snapshot(error, 111, command)) {
|
||||
//TODO: Delete server is it was new
|
||||
return command_result{error::vs_critical, error};
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ if(!result && result.msg().find(ignore) == string::npos){
|
||||
|
||||
#define RESIZE_COLUMN(tblName, rowName, size) up vote EXECUTE("Could not change column size", "ALTER TABLE " tblName " ALTER COLUMN " rowName " varchar(" size ")");
|
||||
|
||||
#define CURRENT_DATABASE_VERSION 12
|
||||
#define CURRENT_DATABASE_VERSION 14
|
||||
#define CURRENT_PERMISSION_VERSION 4
|
||||
|
||||
#define CLIENT_UID_LENGTH "64"
|
||||
@ -79,6 +79,25 @@ do { \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
template <typename T, size_t N>
|
||||
inline bool execute_commands(sql::SqlManager* sql, std::string& error, const std::array<T, N>& commands) {
|
||||
std::string insert_or_ignore{sql->getType() == sql::TYPE_SQLITE ? "INSERT OR IGNORE" : "INSERT IGNORE"};
|
||||
for(const auto& cmd : commands) {
|
||||
std::string command{cmd};
|
||||
|
||||
if(command.starts_with("[INSERT_OR_IGNORE]"))
|
||||
command = insert_or_ignore + command.substr(18);
|
||||
|
||||
auto result = sql::command(sql, command).execute();
|
||||
if(!result) {
|
||||
error = result.fmtStr();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SqlDataManager::initialize(std::string& error) {
|
||||
if(ts::config::database::url.find("sqlite://") == 0)
|
||||
this->manager = new sql::sqlite::SqliteManager();
|
||||
@ -333,10 +352,8 @@ bool SqlDataManager::update_database(std::string &error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(manager->getType() == sql::TYPE_SQLITE) {
|
||||
DROP_INDEX("permissions", "serverId");
|
||||
DROP_INDEX2R("permissions", "serverId", "channelId");
|
||||
}
|
||||
DROP_INDEX("permissions", "serverId");
|
||||
DROP_INDEX2R("permissions", "serverId", "channelId");
|
||||
CREATE_INDEX("permissions", "serverId");
|
||||
CREATE_INDEX2R("permissions", "serverId", "channelId");
|
||||
|
||||
@ -362,10 +379,9 @@ bool SqlDataManager::update_database(std::string &error) {
|
||||
error = "Failed to insert unique properties (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
if(manager->getType() == sql::TYPE_SQLITE) {
|
||||
DROP_INDEX("properties", "serverId");
|
||||
DROP_INDEX2R("properties", "serverId", "id");
|
||||
}
|
||||
|
||||
DROP_INDEX("properties", "serverId");
|
||||
DROP_INDEX2R("properties", "serverId", "id");
|
||||
CREATE_INDEX("properties", "serverId");
|
||||
CREATE_INDEX2R("properties", "serverId", "id");
|
||||
|
||||
@ -412,71 +428,6 @@ bool SqlDataManager::update_database(std::string &error) {
|
||||
CREATE_INDEX2R("conversation_blocks", "server_id", "conversation_id");
|
||||
db_version(11);
|
||||
|
||||
#if 0 /* not yet needed */
|
||||
case 11:
|
||||
/* update the group table */
|
||||
{
|
||||
result = sql::command(this->sql(), "CREATE TABLE `groups_v2` (`serverId` INT NOT NULL, `groupId` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, `target` INT, `type` INT, `displayName` VARCHAR(128));").execute();
|
||||
if(!result) {
|
||||
error = "failed to create new groups table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "INSERT INTO `groups_v2`(`serverId`, `groupId`, `target`, `type`, `displayName`) SELECT `serverId`, `groupId`, `target`, `type`, `displayName` FROM `groups`;").execute();
|
||||
if(!result) {
|
||||
sql::command(this->sql(), "DROP TABLE `groups_v2`;").execute();
|
||||
error = "failed to insert data into the new groups table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "DROP TABLE `groups`;").execute();
|
||||
if(!result) {
|
||||
error = "failed to delete old groups table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "ALTER TABLE `groups_v2` RENAME TO groups;").execute();
|
||||
if(!result) {
|
||||
error = "failed to rename new groups table to the old groups table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
CREATE_INDEX2R("groups", "serverId", "groupId");
|
||||
CREATE_INDEX("groups", "serverId");
|
||||
result = sql::command(this->sql(), "ALTER TABLE `groups` ADD COLUMN `original_id` INTEGER DEFAULT 0;").execute();
|
||||
if(!result) {
|
||||
error = "Failed to alter groups table";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* update the client table */
|
||||
{
|
||||
result = sql::command(this->sql(), "CREATE TABLE `clients_v2` (`serverId` INT NOT NULL, `cldbid` INTEGER, `original_client_id` INTEGER DEFAULT 0, `clientUid` VARCHAR(64) NOT NULL, `firstConnect` BIGINT DEFAULT 0, `lastConnect` BIGINT DEFAULT 0, `connections` INT DEFAULT 0, `lastName` VARCHAR(128) DEFAULT '', UNIQUE(`serverId`, `clientUid`));").execute();
|
||||
if(!result) {
|
||||
error = "failed to create new clients table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "INSERT INTO `clients_v2` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) SELECT `serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName` FROM `clients`;").execute();
|
||||
if(!result) {
|
||||
sql::command(this->sql(), "DROP TABLE `groups_v2`;").execute();
|
||||
error = "failed to insert data into the new clients table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "DROP TABLE `clients`;").execute();
|
||||
if(!result) {
|
||||
error = "failed to delete old clients table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "ALTER TABLE `clients_v2` RENAME TO clients;").execute();
|
||||
if(!result) {
|
||||
error = "failed to rename new clients table to the old clients table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
CREATE_INDEX("clients", "serverId");
|
||||
CREATE_INDEX2R("clients", "serverId", "clientUid");
|
||||
CREATE_INDEX2R("clients", "serverId", "cldbid");
|
||||
}
|
||||
db_version(12);
|
||||
#endif
|
||||
case 11:
|
||||
result = sql::command(this->sql(), "UPDATE `letters` SET `created` = 0 WHERE 1;").execute();
|
||||
if(!result) {
|
||||
@ -485,6 +436,87 @@ bool SqlDataManager::update_database(std::string &error) {
|
||||
}
|
||||
|
||||
db_version(12);
|
||||
|
||||
case 12: {
|
||||
constexpr static std::string_view kCreateClientsV2{R"(
|
||||
CREATE TABLE `clients_v2` (
|
||||
`client_database_id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`client_unique_id` VARCHAR(40) UNIQUE,
|
||||
`client_created` BIGINT,
|
||||
`client_login_name` VARCHAR(20) UNIQUE
|
||||
);
|
||||
)"};
|
||||
|
||||
constexpr static std::string_view kCreateClientsServer{R"(
|
||||
CREATE TABLE `clients_server` (
|
||||
`server_id` INTEGER,
|
||||
`client_unique_id` VARCHAR(40) NOT NULL,
|
||||
`client_database_id` INTEGER NOT NULL,
|
||||
|
||||
`client_nickname` VARCHAR(128),
|
||||
`client_ip` VARCHAR(64),
|
||||
|
||||
`client_login_password` VARCHAR(40),
|
||||
|
||||
`client_last_connected` BIGINT DEFAULT 0,
|
||||
`client_created` BIGINT,
|
||||
`client_total_connections` BIGINT DEFAULT 0,
|
||||
|
||||
`client_month_upload` BIGINT DEFAULT 0,
|
||||
`client_month_download` BIGINT DEFAULT 0,
|
||||
`client_total_upload` BIGINT DEFAULT 0,
|
||||
`client_total_download` BIGINT DEFAULT 0,
|
||||
|
||||
`original_client_id` BIGINT DEFAULT 0,
|
||||
PRIMARY KEY(`server_id`, `client_database_id`)
|
||||
);
|
||||
)"};
|
||||
constexpr static std::string_view kInsertClientsGlobal{"[INSERT_OR_IGNORE] INTO `clients_v2` (`client_database_id`, `client_unique_id`, `client_created`) SELECT `cldbid`, `clientUid`, `firstConnect` FROM `clients` WHERE `serverId` = 0;"};
|
||||
constexpr static std::string_view kInsertClientsServer{R"(
|
||||
[INSERT_OR_IGNORE] INTO `clients_server` (`server_id`, `client_unique_id`, `client_database_id`, `client_nickname`, `client_created`, `client_last_connected`, `client_total_connections`)
|
||||
SELECT `serverId`, `clientUid`, `cldbid`, `lastName`, `firstConnect`, `lastConnect`, `connections` FROM `clients`;
|
||||
)"};
|
||||
|
||||
constexpr static std::array<std::string_view, 11> kUpdateCommands{
|
||||
kCreateClientsV2,
|
||||
kCreateClientsServer,
|
||||
kInsertClientsGlobal,
|
||||
kInsertClientsServer,
|
||||
"CREATE INDEX `idx_clients_unique_id` ON `clients_v2` (`client_unique_id`);",
|
||||
"CREATE INDEX `idx_clients_database_id` ON `clients_v2` (`client_database_id`);",
|
||||
"CREATE INDEX `idx_clients_login` ON `clients_v2` (`client_login_name`);",
|
||||
|
||||
"CREATE INDEX `idx_clients_server_server_unique` ON `clients_server` (`server_id`, `client_unique_id`);",
|
||||
"CREATE INDEX `idx_clients_server_server_database` ON `clients_server` (`server_id`, `client_database_id`);",
|
||||
|
||||
"DROP TABLE `clients`;",
|
||||
"ALTER TABLE `clients_v2` RENAME TO `clients`;",
|
||||
};
|
||||
|
||||
if(!execute_commands(this->sql(), error, kUpdateCommands))
|
||||
return false;
|
||||
|
||||
db_version(13);
|
||||
}
|
||||
|
||||
case 13: {
|
||||
constexpr static std::array<std::string_view, 7> kUpdateCommands{
|
||||
"CREATE INDEX `idx_properties_serverid_id_value` ON `properties` (`serverId`, `id`, `key`);",
|
||||
|
||||
"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);",
|
||||
"INSERT INTO `groups_v2` (`serverId`, `groupId`, `target`, `type`, `displayName`) SELECT `serverId`, `groupId`, `target`, `type`, `displayName` FROM `groups`;",
|
||||
"DROP TABLE `groups`;",
|
||||
"ALTER TABLE `groups_v2` RENAME TO groups;",
|
||||
|
||||
"CREATE INDEX `idx_groups_serverId_groupId` ON `groups` (`server_id`);",
|
||||
"CREATE INDEX `idx_groups_serverid` ON `groups` (`server_id`, `groupId`);"
|
||||
};
|
||||
|
||||
if(!execute_commands(this->sql(), error, kUpdateCommands))
|
||||
return false;
|
||||
|
||||
db_version(14);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include "./permission.h"
|
||||
#include "./client.h"
|
||||
#include "./groups.h"
|
||||
#include "./music.h"
|
||||
#include "./snapshot_data.h"
|
||||
#include "../VirtualServerManager.h"
|
||||
#include "../InstanceHandler.h"
|
||||
#include <sql/insert.h>
|
||||
@ -17,294 +19,83 @@ using namespace ts::server;
|
||||
using SnapshotType = ts::server::snapshots::type;
|
||||
using SnapshotVersion = ts::server::snapshots::version_t;
|
||||
|
||||
//FIXME: Music bots & Playlists
|
||||
constexpr static ServerId kSnapshotServerId{0xFFFF};
|
||||
VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot(std::string &error, std::shared_ptr<VirtualServer> server, const command_parser &command) {
|
||||
if(!server) {
|
||||
auto instances = this->serverInstances();
|
||||
if(config::server::max_virtual_server != -1 && instances.size() > config::server::max_virtual_server)
|
||||
return SnapshotDeployResult::REACHED_CONFIG_SERVER_LIMIT;
|
||||
|
||||
bool VirtualServerManager::deploy_snapshot(std::string &error, ServerId server_id, const command_parser &data) {
|
||||
if(data.bulk(0).has_key("version")) {
|
||||
return this->deploy_ts3_snapshot(error, server_id, data);
|
||||
} else if(data.bulk(1).has_key("snapshot_version")) {
|
||||
/* teaspeak snapshot */
|
||||
return this->deploy_teaspeak_snapshot(error, server_id, data);
|
||||
if(instances.size() >= 65534)
|
||||
return SnapshotDeployResult::REACHED_SOFTWARE_SERVER_LIMIT;
|
||||
}
|
||||
|
||||
bool success{true};
|
||||
auto target_server_id = server ? server->getServerId() : this->next_available_server_id(success);
|
||||
if(!success) {
|
||||
return SnapshotDeployResult::REACHED_SERVER_ID_LIMIT;
|
||||
}
|
||||
|
||||
this->delete_server_in_db(kSnapshotServerId, true);
|
||||
auto result = sql::command{this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)"}
|
||||
.value(":sid", kSnapshotServerId)
|
||||
.value(":host", server ? server->properties()[property::VIRTUALSERVER_HOST].value() : config::binding::DefaultVoiceHost)
|
||||
.value(":port", server ? server->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>() : config::voice::default_voice_port)
|
||||
.execute();
|
||||
|
||||
if(!result) {
|
||||
error = "failed to register the server (" + result.fmtStr() + ")";
|
||||
return SnapshotDeployResult::CUSTOM_ERROR;
|
||||
}
|
||||
|
||||
if(!this->try_deploy_snapshot(error, kSnapshotServerId, target_server_id, command)) {
|
||||
return SnapshotDeployResult::CUSTOM_ERROR;
|
||||
}
|
||||
|
||||
if(!server) {
|
||||
this->delete_server_in_db(target_server_id, true);
|
||||
this->change_server_id_in_db(kSnapshotServerId, target_server_id);
|
||||
} else {
|
||||
/* old TS3 snapshot format */
|
||||
return this->deploy_ts3_snapshot(error, server_id, data);
|
||||
this->deleteServer(server);
|
||||
this->change_server_id_in_db(kSnapshotServerId, target_server_id);
|
||||
}
|
||||
|
||||
server = std::make_shared<VirtualServer>(target_server_id, this->handle->getSql());
|
||||
server->self = server;
|
||||
if(!server->initialize(true)) {
|
||||
//FIXME error handling
|
||||
}
|
||||
|
||||
{
|
||||
threads::MutexLock l(this->instanceLock);
|
||||
this->instances.push_back(server);
|
||||
}
|
||||
this->adjust_executor_threads();
|
||||
return SnapshotDeployResult::SUCCESS;
|
||||
}
|
||||
|
||||
bool VirtualServerManager::deploy_teaspeak_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
||||
if(!data.bulk(1).has_key("snapshot_version")) {
|
||||
error = "Missing snapshot version";
|
||||
return false;
|
||||
}
|
||||
auto version = data.bulk(1).value_as<snapshots::version_t>("snapshot_version");
|
||||
auto hash = data.bulk(0).value("hash");
|
||||
|
||||
if(version < 1) {
|
||||
error = "snapshot version too old";
|
||||
return false;
|
||||
} else if(version > 2) {
|
||||
error = "snapshot version is too new";
|
||||
return false;
|
||||
}
|
||||
|
||||
/* the actual snapshot begins at index 2 */
|
||||
return this->deploy_raw_snapshot(error, server_id, data, hash, 2, SnapshotType::TEASPEAK, version);
|
||||
#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::deploy_ts3_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
||||
snapshots::version_t version{0};
|
||||
if(data.bulk(0).has_key("version"))
|
||||
version = data.bulk(0).value_as<snapshots::version_t>("version");
|
||||
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{};
|
||||
|
||||
auto hash = data.bulk(0).value("hash");
|
||||
if(data.bulk(0).has_key("salt")) {
|
||||
error = "TeaSpeak dosn't support encrypted snapshots yet";
|
||||
if(!snapshots::parse_snapshot(snapshot_data, error, logging_server_id, command))
|
||||
return false;
|
||||
}
|
||||
|
||||
if(version == 0) {
|
||||
return this->deploy_raw_snapshot(error, server_id, data, hash, 1, SnapshotType::TEAMSPEAK, version);
|
||||
} else if(version == 1) {
|
||||
error = "version 1 is an invalid version";
|
||||
return false;
|
||||
} else if(version == 2) {
|
||||
/* compressed data */
|
||||
error = "version 2 isn't currently supported";
|
||||
return false;
|
||||
} else if(version == 3) {
|
||||
error = "version 3 isn't currently supported";
|
||||
return false;
|
||||
} else {
|
||||
error = "snapshots with version 1-3 are currently supported";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct parse_client_entry {
|
||||
snapshots::client_entry parsed_data{};
|
||||
};
|
||||
|
||||
struct parsed_group_entry {
|
||||
snapshots::group_entry parsed_data{};
|
||||
};
|
||||
|
||||
bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &command, const std::string& /* hash */, size_t command_offset,
|
||||
snapshots::type type, snapshots::version_t version) {
|
||||
snapshots::server_entry parsed_server{};
|
||||
//TODO: Verify hash
|
||||
|
||||
/* all snapshots start with the virtual server properties */
|
||||
{
|
||||
snapshots::server_parser parser{type, version, command};
|
||||
if(!parser.parse(error, parsed_server, command_offset))
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<snapshots::channel_entry> parsed_channels{};
|
||||
/* afterwards all channels */
|
||||
{
|
||||
snapshots::channel_parser parser{type, version, command};
|
||||
auto data = command.bulk(command_offset);
|
||||
if(!data.has_key("begin_channels")) {
|
||||
error = "missing begin channels token at " + std::to_string(data.command_character_index());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto end_bulk = command.next_bulk_containing("end_channels", command_offset);
|
||||
if(!end_bulk.has_value()) {
|
||||
error = "missing end channels token";
|
||||
return false;
|
||||
} else if(*end_bulk == command_offset) {
|
||||
error = "snapshot contains no channels";
|
||||
return false;
|
||||
}
|
||||
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")) {
|
||||
auto& entry = parsed_channels.emplace_back();
|
||||
if(!parser.parse(error, entry, command_offset))
|
||||
return false;
|
||||
}
|
||||
command_offset++; /* the "end_channels" token */
|
||||
}
|
||||
|
||||
std::vector<parse_client_entry> parsed_clients{};
|
||||
/* after channels all clients */
|
||||
{
|
||||
snapshots::client_parser parser{type, version, command};
|
||||
auto data = command.bulk(command_offset);
|
||||
if(!data.has_key("begin_clients")) {
|
||||
error = "missing begin clients token at " + std::to_string(data.command_character_index());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto end_bulk = command.next_bulk_containing("end_clients", command_offset);
|
||||
if(!end_bulk.has_value()) {
|
||||
error = "missing end clients token";
|
||||
return false;
|
||||
}
|
||||
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")) {
|
||||
auto& entry = parsed_clients.emplace_back();
|
||||
if(!parser.parse(error, entry.parsed_data, command_offset))
|
||||
return false;
|
||||
}
|
||||
command_offset++; /* the "end_clients" token */
|
||||
}
|
||||
|
||||
bool server_groups_parsed{false},
|
||||
channel_groups_parsed{false},
|
||||
client_permissions_parsed{false},
|
||||
channel_permissions_parsed{false},
|
||||
client_channel_permissions_parsed{false};
|
||||
|
||||
std::vector<parsed_group_entry> parsed_server_groups{};
|
||||
snapshots::group_relations parsed_server_group_relations{};
|
||||
|
||||
std::vector<parsed_group_entry> parsed_channel_groups{};
|
||||
snapshots::group_relations parsed_channel_group_relations{};
|
||||
|
||||
std::deque<snapshots::permissions_flat_entry> client_permissions{};
|
||||
std::deque<snapshots::permissions_flat_entry> channel_permissions{};
|
||||
std::deque<snapshots::permissions_flat_entry> client_channel_permissions{};
|
||||
|
||||
/* permissions */
|
||||
{
|
||||
if(!command.bulk(command_offset++).has_key("begin_permissions")) {
|
||||
error = "missing begin permissions key";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::relation_parser relation_parser{type, version, command};
|
||||
while(!command.bulk(command_offset).has_key("end_permissions")) {
|
||||
if(command.bulk(command_offset).has_key("server_groups")) {
|
||||
if(server_groups_parsed) {
|
||||
error = "duplicated server group list";
|
||||
return false;
|
||||
} else server_groups_parsed = true;
|
||||
snapshots::group_parser group_parser{type, version, command, "id", permission::teamspeak::GroupType::SERVER};
|
||||
|
||||
/* parse all groups */
|
||||
while(!command.bulk(command_offset).has_key("end_groups")){
|
||||
auto& group = parsed_server_groups.emplace_back();
|
||||
if(!group_parser.parse(error, group.parsed_data, command_offset)) /* will consume the end group token */
|
||||
return false;
|
||||
command_offset++; /* for the "end_group" token */
|
||||
}
|
||||
command_offset++; /* for the "end_groups" token */
|
||||
|
||||
/* parse relations */
|
||||
if(!relation_parser.parse(error, parsed_server_group_relations, command_offset))
|
||||
return false;
|
||||
command_offset++; /* for the "end_relations" token */
|
||||
|
||||
if(parsed_server_group_relations.size() > 1) {
|
||||
error = "all group relations should be for channel id 0 but received more than one different channel.";
|
||||
return false;
|
||||
} else if(!parsed_server_group_relations.empty() && parsed_server_group_relations.begin()->first != 0) {
|
||||
error = "all group relations should be for channel id 0 but received it for " + std::to_string(parsed_server_group_relations.begin()->first);
|
||||
return false;
|
||||
}
|
||||
} else if(command.bulk(command_offset).has_key("channel_groups")) {
|
||||
if(channel_groups_parsed) {
|
||||
error = "duplicated channel group list";
|
||||
return false;
|
||||
} else channel_groups_parsed = true;
|
||||
snapshots::group_parser group_parser{type, version, command, "id", permission::teamspeak::GroupType::CHANNEL};
|
||||
|
||||
/* parse all groups */
|
||||
while(!command.bulk(command_offset).has_key("end_groups")){
|
||||
auto& group = parsed_channel_groups.emplace_back();
|
||||
if(!group_parser.parse(error, group.parsed_data, command_offset))
|
||||
return false;
|
||||
command_offset++; /* for the "end_group" token */
|
||||
}
|
||||
command_offset++; /* for the "end_groups" token */
|
||||
|
||||
/* parse relations */
|
||||
if(!relation_parser.parse(error, parsed_channel_group_relations, command_offset))
|
||||
return false;
|
||||
command_offset++; /* for the "end_relations" token */
|
||||
} else if(command.bulk(command_offset).has_key("client_flat")) {
|
||||
/* client permissions */
|
||||
if(client_permissions_parsed) {
|
||||
error = "duplicated client permissions list";
|
||||
return false;
|
||||
} else client_permissions_parsed = true;
|
||||
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CLIENT};
|
||||
if(!flat_parser.parse(error, client_permissions, command_offset))
|
||||
return false;
|
||||
command_offset++; /* for the "end_flat" token */
|
||||
} else if(command.bulk(command_offset).has_key("channel_flat")) {
|
||||
/* channel permissions */
|
||||
if(channel_permissions_parsed) {
|
||||
error = "duplicated channel permissions list";
|
||||
return false;
|
||||
} else channel_permissions_parsed = true;
|
||||
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CHANNEL};
|
||||
if(!flat_parser.parse(error, channel_permissions, command_offset))
|
||||
return false;
|
||||
|
||||
command_offset++; /* for the "end_flat" token */
|
||||
} else if(command.bulk(command_offset).has_key("channel_client_flat")) {
|
||||
/* channel client permissions */
|
||||
if(client_channel_permissions_parsed) {
|
||||
error = "duplicated client channel permissions list";
|
||||
return false;
|
||||
} else client_channel_permissions_parsed = true;
|
||||
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CLIENT};
|
||||
if(!flat_parser.parse(error, client_channel_permissions, command_offset))
|
||||
return false;
|
||||
|
||||
command_offset++; /* for the "end_flat" token */
|
||||
} else {
|
||||
command_offset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check if everything has been parsed */
|
||||
{
|
||||
/* basic stuff */
|
||||
if(!server_groups_parsed) {
|
||||
error = "missing server groups";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!channel_groups_parsed) {
|
||||
error = "missing channel groups";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!client_permissions_parsed) {
|
||||
error = "missing client permissions";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!channel_permissions_parsed) {
|
||||
error = "missing channel permissions";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!client_channel_permissions_parsed) {
|
||||
error = "missing client channel permissions";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::map<ClientDbId, ChannelId> client_id_mapping{};
|
||||
std::map<ClientDbId, ClientDbId> client_id_mapping{};
|
||||
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 */
|
||||
{
|
||||
/* cleanup all old data */
|
||||
this->delete_server_in_db(server_id);
|
||||
|
||||
/* register server & properties */
|
||||
{
|
||||
|
||||
@ -316,11 +107,11 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
sql::Column<std::string>("value")
|
||||
};
|
||||
|
||||
for(const auto& property : parsed_server.properties.list_properties(property::FLAG_SAVE)) {
|
||||
for(const auto& property : snapshot_data.parsed_server.properties.list_properties(property::FLAG_SAVE)) {
|
||||
if(!property.isModified()) continue;
|
||||
|
||||
insert_property_query.add_entry(
|
||||
server_id,
|
||||
target_server_id,
|
||||
property::PROP_TYPE_SERVER,
|
||||
0,
|
||||
std::string{property.type().name},
|
||||
@ -330,118 +121,79 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
|
||||
{
|
||||
auto result = insert_property_query.execute(this->handle->getSql(), true);
|
||||
if(result.failed_entries.size() > 0)
|
||||
logWarning(server_id, "Failed to insert all server properties into the database. Failed property count: {}", result.failed_entries.size());
|
||||
}
|
||||
|
||||
//FIXME: Override host & port
|
||||
auto result = sql::command{this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)"}
|
||||
.value(":sid", server_id)
|
||||
.value(":host", parsed_server.properties[property::VIRTUALSERVER_HOST].value())
|
||||
.value(":port", parsed_server.properties[property::VIRTUALSERVER_PORT].value())
|
||||
.execute();
|
||||
if(!result) {
|
||||
error = "failed to register the server (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
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 */
|
||||
/* register clients and bots */
|
||||
{
|
||||
sql::InsertQuery insert_general_query{"clients",
|
||||
sql::Column<ServerId>("serverId"),
|
||||
sql::Column<ClientDbId>("cldbid"),
|
||||
sql::Column<std::string>("clientUid")
|
||||
sql::Column<std::string>("client_unique_id"),
|
||||
sql::Column<int64_t>("client_created")
|
||||
};
|
||||
|
||||
sql::InsertQuery insert_server_query{"clients",
|
||||
sql::Column<ServerId>("serverId"),
|
||||
sql::Column<ClientDbId>("cldbid"),
|
||||
sql::Column<ClientDbId>("original_client_id"),
|
||||
sql::Column<std::string>("clientUid"),
|
||||
sql::Column<int64_t>("firstConnect"),
|
||||
sql::Column<int64_t>("lastConnect"),
|
||||
sql::Column<uint64_t>("connections"),
|
||||
sql::Column<std::string>("lastName")
|
||||
};
|
||||
|
||||
{
|
||||
auto update_result = sql::command(this->handle->getSql(), "UPDATE `clients` SET `original_client_id` = 0 WHERE `original_client_id` = :sid AND `serverId` = 0", variable{":sid", server_id}).execute();
|
||||
if(!update_result)
|
||||
logWarning(server_id, "Failed to reset client id mapping for this server: {}", update_result.fmtStr());
|
||||
}
|
||||
|
||||
std::unique_lock db_id_lock{DatabaseHelper::database_id_mutex};
|
||||
ClientDbId new_client_database_id{0};
|
||||
{
|
||||
auto query_result = sql::command(this->handle->getSql(), "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([&](int length, std::string* values, std::string* names) {
|
||||
assert(length == 1);
|
||||
new_client_database_id = (ClientDbId) stoll(values[0]);
|
||||
});
|
||||
if(!query_result) {
|
||||
error = "failed to query the current client database index (" + query_result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& client : parsed_clients) {
|
||||
auto current_time_seconds = std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
for(const auto& client : snapshot_data.parsed_clients) {
|
||||
insert_general_query.add_entry(
|
||||
0,
|
||||
++new_client_database_id, /* this might, or might not be used as new id */
|
||||
client.parsed_data.unique_id
|
||||
client.unique_id,
|
||||
std::chrono::floor<std::chrono::seconds>(client.timestamp_created.time_since_epoch()).count()
|
||||
);
|
||||
|
||||
/* we're updating the database id afterwards */
|
||||
insert_server_query.add_entry(
|
||||
server_id,
|
||||
client.parsed_data.database_id, /* to keep stuff unique */
|
||||
client.parsed_data.database_id,
|
||||
client.parsed_data.unique_id,
|
||||
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_created.time_since_epoch()).count(),
|
||||
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_last_connected.time_since_epoch()).count(),
|
||||
client.parsed_data.client_total_connections,
|
||||
client.parsed_data.nickname
|
||||
}
|
||||
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);
|
||||
for(const auto& fail : result.failed_entries)
|
||||
logWarning(server_id, "Failed to insert client {} into the general database: {}", parsed_clients[std::get<0>(fail)].parsed_data.database_id, std::get<1>(fail).fmtStr());
|
||||
}
|
||||
db_id_lock.unlock();
|
||||
auto result = insert_general_query.execute(this->handle->getSql(), true);
|
||||
for(const auto& fail : result.failed_entries)
|
||||
logWarning(logging_server_id, "Failed to insert client {} into the general client database: {} (This should not happen!)", snapshot_data.parsed_clients[std::get<0>(fail)].database_id, std::get<1>(fail).fmtStr());
|
||||
|
||||
{
|
||||
auto result = insert_server_query.execute(this->handle->getSql(), true);
|
||||
for(const auto& fail : result.failed_entries)
|
||||
logWarning(server_id, "Failed to insert client {} into the server database: {}", parsed_clients[std::get<0>(fail)].parsed_data.database_id, std::get<1>(fail).fmtStr());
|
||||
|
||||
sql::model insert_command{this->handle->getSql(),
|
||||
"INSERT INTO `clients_server` (`server_id`, `client_unique_id`, `client_database_id`, `client_created`, `original_client_id`) SELECT :serverId, :uniqueId, `client_database_id`, :timestamp, :org_id FROM `clients` WHERE `client_unique_id` = :uniqueId;"};
|
||||
|
||||
insert_command.value(":serverId", target_server_id);
|
||||
|
||||
for(const auto& client : snapshot_data.parsed_clients) {
|
||||
auto sqlResult = insert_command.command().values(
|
||||
variable{":uniqueId", client.unique_id},
|
||||
variable{":timestamp", std::chrono::floor<std::chrono::seconds>(client.timestamp_created.time_since_epoch()).count()},
|
||||
variable{":org_id", client.database_id}
|
||||
).execute();
|
||||
|
||||
if(!sqlResult)
|
||||
logWarning(logging_server_id, "Failed to insert client {} ({}) into the database: {}", client.unique_id, client.nickname, sqlResult.fmtStr());
|
||||
}
|
||||
|
||||
{
|
||||
auto update_result = sql::command{this->handle->getSql(), "UPDATE `clients` SET `cldbid` = (SELECT `cldbid` from `clients` AS `t` WHERE `t`.`clientUid` = `clients`.`clientUid` LIMIT 1) WHERE `serverId` = :sid;"}
|
||||
.value(":sid", server_id)
|
||||
.execute();
|
||||
if(!update_result) {
|
||||
error = "failed to update client database ids (" + update_result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
for(const auto& client : snapshot_data.music_bots) {
|
||||
auto sqlResult = insert_command.command().values(
|
||||
variable{":uniqueId", client.unique_id},
|
||||
variable{":timestamp", current_time_seconds},
|
||||
variable{":org_id", client.database_id}
|
||||
).execute();
|
||||
|
||||
if(!sqlResult)
|
||||
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`,`cldbid` FROM `clients` WHERE `serverId` = :sid;"}
|
||||
.value(":serverId", server_id)
|
||||
auto map_query_result = sql::command{this->handle->getSql(), "SELECT `original_client_id`,`client_database_id` FROM `clients_server` WHERE `server_id` = :sid;"}
|
||||
.value(":serverId", target_server_id)
|
||||
.query([&](int length, std::string* values, std::string* names) {
|
||||
ClientDbId original_id{0}, new_id{0};
|
||||
try {
|
||||
original_id = std::stoull(values[0]);
|
||||
new_id = std::stoull(values[1]);
|
||||
} catch (std::exception& ex) {
|
||||
logWarning(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;
|
||||
}
|
||||
client_id_mapping[original_id] = new_id;
|
||||
});
|
||||
if(!map_query_result) {
|
||||
error = "failed to query client dabase id mappings (" + map_query_result.fmtStr() + ")";
|
||||
error = "failed to query client database id mappings (" + map_query_result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -450,14 +202,14 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
{
|
||||
/* Assign each channel a new id */
|
||||
ChannelId current_id{0};
|
||||
for(auto& channel : parsed_channels) {
|
||||
for(auto& channel : snapshot_data.parsed_channels) {
|
||||
const auto new_id = current_id++;
|
||||
channel_id_mapping[channel.properties[property::CHANNEL_ID]] = new_id;
|
||||
channel.properties[property::CHANNEL_ID] = new_id;
|
||||
}
|
||||
|
||||
/* Update channel parents */
|
||||
for(auto& channel : parsed_channels) {
|
||||
for(auto& channel : snapshot_data.parsed_channels) {
|
||||
auto pid = channel.properties[property::CHANNEL_PID].as<ChannelId>();
|
||||
if(pid > 0) {
|
||||
auto new_id = channel_id_mapping.find(pid);
|
||||
@ -484,10 +236,10 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
sql::Column<std::string>("value")
|
||||
};
|
||||
|
||||
for(auto& channel : parsed_channels) {
|
||||
for(auto& channel : snapshot_data.parsed_channels) {
|
||||
auto channel_id = channel.properties[property::CHANNEL_ID].as<ChannelId>();
|
||||
insert_query.add_entry(
|
||||
server_id,
|
||||
target_server_id,
|
||||
channel_id,
|
||||
channel.properties[property::CHANNEL_PID].as<ChannelId>()
|
||||
);
|
||||
@ -496,7 +248,7 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
if(!property.isModified()) continue;
|
||||
|
||||
insert_property_query.add_entry(
|
||||
server_id,
|
||||
target_server_id,
|
||||
property::PROP_TYPE_CHANNEL,
|
||||
channel_id,
|
||||
std::string{property.type().name},
|
||||
@ -508,13 +260,13 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
{
|
||||
auto result = insert_query.execute(this->handle->getSql(), true);
|
||||
for(const auto& fail : result.failed_entries)
|
||||
logWarning(server_id, "Failed to insert channel {} into the server database: {}", parsed_channels[std::get<0>(fail)].properties[property::CHANNEL_NAME].value(), std::get<1>(fail).fmtStr());
|
||||
logWarning(logging_server_id, "Failed to insert channel {} into the server database: {}", snapshot_data.parsed_channels[std::get<0>(fail)].properties[property::CHANNEL_NAME].value(), std::get<1>(fail).fmtStr());
|
||||
}
|
||||
|
||||
{
|
||||
auto result = insert_property_query.execute(this->handle->getSql(), true);
|
||||
if(result.failed_entries.size() > 0)
|
||||
logWarning(server_id, "Failed to insert all channel properties into the database. Failed property cound: {}", result.failed_entries.size());
|
||||
if(!result.failed_entries.empty())
|
||||
logWarning(logging_server_id, "Failed to insert some channel properties into the database. Failed property count: {}", result.failed_entries.size());
|
||||
}
|
||||
}
|
||||
|
||||
@ -533,11 +285,11 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
sql::Column<bool>("flag_negate")
|
||||
};
|
||||
|
||||
for(auto& entry : channel_permissions) {
|
||||
for(auto& entry : snapshot_data.channel_permissions) {
|
||||
{
|
||||
auto new_id = channel_id_mapping.find(entry.id1);
|
||||
if(new_id == channel_id_mapping.end()) {
|
||||
logWarning(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;
|
||||
}
|
||||
entry.id1 = new_id->second;
|
||||
@ -545,7 +297,7 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
|
||||
for(const auto& permission : entry.permissions) {
|
||||
insert_query.add_entry(
|
||||
server_id,
|
||||
target_server_id,
|
||||
permission::SQL_PERM_CHANNEL,
|
||||
entry.id1,
|
||||
0,
|
||||
@ -560,29 +312,29 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
|
||||
auto result = insert_query.execute(this->handle->getSql(), true);
|
||||
if(!result.failed_entries.empty())
|
||||
logWarning(server_id, "Failed to insert all channel permissions into the database. Failed permission count: {}", result.failed_entries.size());
|
||||
logWarning(logging_server_id, "Failed to insert all channel permissions into the database. Failed permission count: {}", result.failed_entries.size());
|
||||
}
|
||||
|
||||
/* server groups */
|
||||
{
|
||||
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_id`) VALUES (:serverId, :target, :type, :name, :id)"};
|
||||
insert_model.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL);
|
||||
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_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL);
|
||||
|
||||
for(auto& group : parsed_server_groups) {
|
||||
auto result = insert_model.command().value(":name", group.parsed_data.name).value(":id", group.parsed_data.group_id).execute();
|
||||
for(auto& group : snapshot_data.parsed_server_groups) {
|
||||
auto result = insert_model.command().value(":name", group.name).value(":id", group.group_id).execute();
|
||||
if(!result)
|
||||
logWarning(server_id, "Failed to insert server group \"{}\" into the database", group.parsed_data.name);
|
||||
logWarning(logging_server_id, "Failed to insert server group \"{}\" into the database", group.name);
|
||||
}
|
||||
|
||||
sql::command{this->handle->getSql(), "SELECT `original_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
|
||||
.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL)
|
||||
sql::command{this->handle->getSql(), "SELECT `original_group_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
|
||||
.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) {
|
||||
GroupId original_id{0}, new_id{0};
|
||||
try {
|
||||
original_id = std::stoull(values[0]);
|
||||
new_id = std::stoull(values[1]);
|
||||
} catch (std::exception& ex) {
|
||||
logWarning(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;
|
||||
}
|
||||
server_group_id_mapping[original_id] = new_id;
|
||||
@ -598,20 +350,20 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
sql::Column<ChannelId>("channelId")
|
||||
};
|
||||
|
||||
for(auto& relation : parsed_server_group_relations) {
|
||||
for(auto& relation : snapshot_data.parsed_server_group_relations) {
|
||||
for(auto& entry : relation.second) {
|
||||
ClientId client_id{};
|
||||
{
|
||||
auto new_id = client_id_mapping.find(entry.client_id);
|
||||
if(new_id == client_id_mapping.end()) {
|
||||
logWarning(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;
|
||||
}
|
||||
client_id = new_id->second;
|
||||
}
|
||||
|
||||
insert_query.add_entry(
|
||||
server_id,
|
||||
target_server_id,
|
||||
client_id,
|
||||
entry.group_id,
|
||||
0
|
||||
@ -621,29 +373,29 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
|
||||
auto result = insert_query.execute(this->handle->getSql(), true);
|
||||
if(!result.failed_entries.empty())
|
||||
logWarning(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());
|
||||
}
|
||||
|
||||
/* channel groups */
|
||||
{
|
||||
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_id`) VALUES (:serverId, :target, :type, :name, :id)"};
|
||||
insert_model.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL);
|
||||
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);
|
||||
|
||||
for(auto& group : parsed_server_groups) {
|
||||
auto result = insert_model.command().value(":name", group.parsed_data.name).value(":id", group.parsed_data.group_id).execute();
|
||||
for(auto& group : snapshot_data.parsed_server_groups) {
|
||||
auto result = insert_model.command().value(":name", group.name).value(":id", group.group_id).execute();
|
||||
if(!result)
|
||||
logWarning(server_id, "Failed to insert channel group \"{}\" into the database", group.parsed_data.name);
|
||||
logWarning(logging_server_id, "Failed to insert channel group \"{}\" into the database", group.name);
|
||||
}
|
||||
|
||||
sql::command{this->handle->getSql(), "SELECT `original_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
|
||||
.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL)
|
||||
sql::command{this->handle->getSql(), "SELECT `original_group_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
|
||||
.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) {
|
||||
GroupId original_id{0}, new_id{0};
|
||||
try {
|
||||
original_id = std::stoull(values[0]);
|
||||
new_id = std::stoull(values[1]);
|
||||
} catch (std::exception& ex) {
|
||||
logWarning(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;
|
||||
}
|
||||
channel_group_id_mapping[original_id] = new_id;
|
||||
@ -659,12 +411,12 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
sql::Column<ChannelId>("channelId")
|
||||
};
|
||||
|
||||
for(auto& relation : parsed_channel_group_relations) {
|
||||
for(auto& relation : snapshot_data.parsed_channel_group_relations) {
|
||||
ChannelId channel_id{};
|
||||
{
|
||||
auto new_id = channel_id_mapping.find(relation.first);
|
||||
if(new_id == channel_id_mapping.end()) {
|
||||
logWarning(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;
|
||||
}
|
||||
channel_id = new_id->second;
|
||||
@ -675,14 +427,14 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
{
|
||||
auto new_id = client_id_mapping.find(entry.client_id);
|
||||
if(new_id == client_id_mapping.end()) {
|
||||
logWarning(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;
|
||||
}
|
||||
client_id = new_id->second;
|
||||
}
|
||||
|
||||
insert_query.add_entry(
|
||||
server_id,
|
||||
target_server_id,
|
||||
client_id,
|
||||
entry.group_id,
|
||||
channel_id
|
||||
@ -692,7 +444,7 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
|
||||
auto result = insert_query.execute(this->handle->getSql(), true);
|
||||
if(!result.failed_entries.empty())
|
||||
logWarning(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());
|
||||
}
|
||||
|
||||
/* client permissions */
|
||||
@ -710,11 +462,11 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
sql::Column<bool>("flag_negate"),
|
||||
};
|
||||
|
||||
for(auto& entry : client_permissions) {
|
||||
for(auto& entry : snapshot_data.client_permissions) {
|
||||
{
|
||||
auto new_id = client_id_mapping.find(entry.id1);
|
||||
if(new_id == client_id_mapping.end()) {
|
||||
logWarning(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;
|
||||
}
|
||||
entry.id1 = new_id->second;
|
||||
@ -722,7 +474,7 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
|
||||
for(const auto& permission : entry.permissions) {
|
||||
insert_query.add_entry(
|
||||
server_id,
|
||||
target_server_id,
|
||||
permission::SQL_PERM_USER,
|
||||
entry.id1,
|
||||
0,
|
||||
@ -737,7 +489,7 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
|
||||
auto result = insert_query.execute(this->handle->getSql(), true);
|
||||
if(!result.failed_entries.empty())
|
||||
logWarning(server_id, "Failed to insert all client permissions into the database. Failed permission count: {}", result.failed_entries.size());
|
||||
logWarning(logging_server_id, "Failed to insert all client permissions into the database. Failed permission count: {}", result.failed_entries.size());
|
||||
}
|
||||
|
||||
/* client channel permissions */
|
||||
@ -755,11 +507,11 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
sql::Column<bool>("flag_negate"),
|
||||
};
|
||||
|
||||
for(auto& entry : client_channel_permissions) {
|
||||
for(auto& entry : snapshot_data.client_channel_permissions) {
|
||||
{
|
||||
auto new_id = channel_id_mapping.find(entry.id1);
|
||||
if(new_id == channel_id_mapping.end()) {
|
||||
logWarning(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;
|
||||
}
|
||||
entry.id1 = new_id->second;
|
||||
@ -768,7 +520,7 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
{
|
||||
auto new_id = client_id_mapping.find(entry.id2);
|
||||
if(new_id == client_id_mapping.end()) {
|
||||
logWarning(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;
|
||||
}
|
||||
entry.id2 = new_id->second;
|
||||
@ -776,7 +528,7 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
|
||||
for(const auto& permission : entry.permissions) {
|
||||
insert_query.add_entry(
|
||||
server_id,
|
||||
target_server_id,
|
||||
permission::SQL_PERM_USER,
|
||||
entry.id2,
|
||||
(ChannelId) entry.id1,
|
||||
@ -791,9 +543,146 @@ bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId
|
||||
|
||||
auto result = insert_query.execute(this->handle->getSql(), true);
|
||||
if(!result.failed_entries.empty())
|
||||
logWarning(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 bot attributes */
|
||||
{
|
||||
|
||||
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(auto& bot : snapshot_data.music_bots) {
|
||||
auto new_id = client_id_mapping.find(bot.database_id);
|
||||
if(new_id == client_id_mapping.end()) {
|
||||
logWarning(logging_server_id, "Missing client id mapping for music bot (Unique ID: {}). Skipping permission insert.", bot.unique_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
for(const auto& property : bot.properties.list_properties(property::FLAG_SAVE | property::FLAG_SAVE_MUSIC)) {
|
||||
if(!property.isModified()) continue;
|
||||
|
||||
auto value = property.value();
|
||||
if(property.type() == property::CLIENT_LAST_CHANNEL) {
|
||||
auto channel_id = property.as<ChannelId>();
|
||||
|
||||
auto new_channel_id = channel_id_mapping.find(channel_id);
|
||||
if(new_channel_id == channel_id_mapping.end()) {
|
||||
logWarning(logging_server_id, "Missing channel id mapping for channel {} for music bot {}.", channel_id, bot.unique_id);
|
||||
continue;
|
||||
}
|
||||
value = std::to_string(new_channel_id->second);
|
||||
}
|
||||
insert_property_query.add_entry(
|
||||
target_server_id,
|
||||
property::PROP_TYPE_CLIENT,
|
||||
new_id->second,
|
||||
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 some music bot properties into the database. Failed property count: {}", result.failed_entries.size());
|
||||
}
|
||||
|
||||
/* playlists */
|
||||
{
|
||||
|
||||
sql::InsertQuery insert_playlist_query{"playlists",
|
||||
sql::Column<ServerId>("serverId"),
|
||||
sql::Column<PlaylistId>("playlist_id")
|
||||
};
|
||||
|
||||
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(auto& playlist : snapshot_data.playlists) {
|
||||
insert_playlist_query.add_entry(target_server_id, playlist.playlist_id);
|
||||
|
||||
for(const auto& property : playlist.properties.list_properties(property::FLAG_SAVE | property::FLAG_SAVE_MUSIC)) {
|
||||
if(!property.isModified()) continue;
|
||||
|
||||
insert_property_query.add_entry(
|
||||
target_server_id,
|
||||
property::PROP_TYPE_PLAYLIST,
|
||||
playlist.playlist_id,
|
||||
std::string{property.type().name},
|
||||
property.value()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto result = insert_playlist_query.execute(this->handle->getSql(), true);
|
||||
if(!result.failed_entries.empty())
|
||||
logWarning(logging_server_id, "Failed to insert some playlists into the database. Failed playlist count: {}", result.failed_entries.size());
|
||||
}
|
||||
|
||||
{
|
||||
auto result = insert_property_query.execute(this->handle->getSql(), true);
|
||||
if(!result.failed_entries.empty())
|
||||
logWarning(logging_server_id, "Failed to insert some playlist properties into the database. Failed playlist count: {}", result.failed_entries.size());
|
||||
}
|
||||
}
|
||||
|
||||
/* playlist songs */
|
||||
{
|
||||
sql::InsertQuery insert_song_query{"playlist_songs",
|
||||
sql::Column<ServerId>("serverId"),
|
||||
sql::Column<PlaylistId>("playlist_id"),
|
||||
sql::Column<SongId>("song_id"),
|
||||
sql::Column<SongId>("order_id"),
|
||||
sql::Column<ClientDbId>("invoker_dbid"),
|
||||
sql::Column<std::string>("url"),
|
||||
sql::Column<std::string>("url_loader"),
|
||||
sql::Column<bool>("loaded"),
|
||||
sql::Column<std::string>("metadata")
|
||||
};
|
||||
|
||||
for(auto& song : snapshot_data.playlist_songs) {
|
||||
{
|
||||
auto new_id = client_id_mapping.find(song.invoker_id);
|
||||
if(new_id == client_id_mapping.end()) {
|
||||
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;
|
||||
} else {
|
||||
song.invoker_id = new_id->second;
|
||||
}
|
||||
}
|
||||
|
||||
insert_song_query.add_entry(
|
||||
target_server_id,
|
||||
song.playlist_id,
|
||||
song.song_id,
|
||||
song.order_id,
|
||||
song.invoker_id,
|
||||
song.url,
|
||||
song.loader,
|
||||
song.loaded,
|
||||
song.metadata
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
auto result = insert_song_query.execute(this->handle->getSql(), true);
|
||||
if(!result.failed_entries.empty())
|
||||
logWarning(logging_server_id, "Failed to insert some playlist songs into the database. Failed song count: {}", result.failed_entries.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
error = "not implemented";
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
102
server/src/snapshots/music.cpp
Normal file
102
server/src/snapshots/music.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// Created by WolverinDEV on 30/07/2020.
|
||||
//
|
||||
|
||||
#include "music.h"
|
||||
|
||||
using namespace ts::server::snapshots;
|
||||
|
||||
bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t &index) {
|
||||
auto data = this->command[index];
|
||||
|
||||
size_t entry_index{0};
|
||||
std::string_view key{};
|
||||
std::string value{};
|
||||
while(data.next_entry(entry_index, key, value)) {
|
||||
if(key == "bot_id") {
|
||||
char* end_ptr{nullptr};
|
||||
entry.database_id = strtoull(value.c_str(), &end_ptr, 10);
|
||||
if (*end_ptr) {
|
||||
error = "failed to parse bot database id at character " + std::to_string(data.key_command_character_index(key) + key.length());
|
||||
return false;
|
||||
}
|
||||
} else if(key == "bot_unique_id") {
|
||||
entry.unique_id = value;
|
||||
} else if(key == "bot_owner_id") {
|
||||
entry.bot_owner_id = value;
|
||||
} else if(key == "end_bots") {
|
||||
continue;
|
||||
} else {
|
||||
const auto& property = property::find<property::ClientProperties>(key);
|
||||
if(property.is_undefined()) {
|
||||
//TODO: Issue a warning
|
||||
continue;
|
||||
}
|
||||
|
||||
//TODO: Validate value
|
||||
entry.properties[property] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if(entry.database_id == 0) {
|
||||
error = "missing database id at entry " + std::to_string(data.command_character_index());
|
||||
return false;
|
||||
} else if(entry.unique_id.empty()) {
|
||||
error = "missing unique id at entry " + std::to_string(data.command_character_index());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool playlist_parser::parse(std::string &error, playlist_entry &entry, size_t &index) {
|
||||
auto data = this->command[index];
|
||||
|
||||
size_t entry_index{0};
|
||||
std::string_view key{};
|
||||
std::string value{};
|
||||
while(data.next_entry(entry_index, key, value)) {
|
||||
if(key == "playlist_id") {
|
||||
char* end_ptr{nullptr};
|
||||
entry.playlist_id = strtoull(value.c_str(), &end_ptr, 10);
|
||||
if (*end_ptr) {
|
||||
error = "failed to parse playlist id at character " + std::to_string(data.key_command_character_index(key) + key.length());
|
||||
return false;
|
||||
}
|
||||
} else if(key == "end_playlist") {
|
||||
continue;
|
||||
} else {
|
||||
const auto& property = property::find<property::PlaylistProperties>(key);
|
||||
if(property.is_undefined()) {
|
||||
//TODO: Issue a warning
|
||||
continue;
|
||||
}
|
||||
|
||||
//TODO: Validate value
|
||||
entry.properties[property] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if(entry.playlist_id == 0) {
|
||||
error = "missing playlist id at entry " + std::to_string(data.command_character_index());
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool playlist_song_parser::parse(std::string &error, playlist_song_entry &entry, size_t &index) {
|
||||
auto data = this->command[index];
|
||||
|
||||
entry.song_id = data.value_as<SongId>("song_id");
|
||||
entry.order_id = data.value_as<SongId>("song_order");
|
||||
entry.invoker_id = data.value_as<ClientDbId>("song_invoker");
|
||||
|
||||
entry.url = data.value("song_url");
|
||||
entry.loader = data.value("song_url_loader");
|
||||
entry.loaded = data.value_as<bool>("song_loaded");
|
||||
|
||||
entry.metadata = data.value("song_metadata");
|
||||
|
||||
return true;
|
||||
}
|
70
server/src/snapshots/music.h
Normal file
70
server/src/snapshots/music.h
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <Definitions.h>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <Properties.h>
|
||||
#include "./snapshot.h"
|
||||
|
||||
namespace ts::server::snapshots {
|
||||
struct music_bot_entry {
|
||||
ClientDbId database_id{0};
|
||||
|
||||
std::string unique_id{};
|
||||
std::string bot_owner_id{};
|
||||
|
||||
Properties properties{};
|
||||
};
|
||||
|
||||
struct playlist_entry {
|
||||
PlaylistId playlist_id{0};
|
||||
Properties properties{};
|
||||
};
|
||||
|
||||
struct playlist_song_entry {
|
||||
PlaylistId playlist_id{0}; /* Attention: This will not be written into the snapshot directly! */
|
||||
SongId song_id{0};
|
||||
SongId order_id{0};
|
||||
|
||||
ClientDbId invoker_id{0};
|
||||
|
||||
std::string url{};
|
||||
std::string loader{};
|
||||
bool loaded{false};
|
||||
|
||||
std::string metadata{};
|
||||
};
|
||||
|
||||
class music_bot_parser : public parser<music_bot_entry> {
|
||||
public:
|
||||
music_bot_parser(type type_, version_t version, const command_parser &command) : parser{type_, version,
|
||||
command} {}
|
||||
|
||||
bool parse(
|
||||
std::string & /* error */,
|
||||
music_bot_entry & /* result */,
|
||||
size_t & /* offset */) override;
|
||||
};
|
||||
|
||||
class playlist_parser : public parser<playlist_entry> {
|
||||
public:
|
||||
playlist_parser(type type_, version_t version, const command_parser &command) : parser{type_, version,
|
||||
command} {}
|
||||
|
||||
bool parse(
|
||||
std::string & /* error */,
|
||||
playlist_entry & /* result */,
|
||||
size_t & /* offset */) override;
|
||||
};
|
||||
|
||||
class playlist_song_parser : public parser<playlist_song_entry> {
|
||||
public:
|
||||
playlist_song_parser(type type_, version_t version, const command_parser &command) : parser{type_, version,
|
||||
command} {}
|
||||
|
||||
bool parse(
|
||||
std::string & /* error */,
|
||||
playlist_song_entry & /* result */,
|
||||
size_t & /* offset */) override;
|
||||
};
|
||||
}
|
355
server/src/snapshots/parser.cpp
Normal file
355
server/src/snapshots/parser.cpp
Normal file
@ -0,0 +1,355 @@
|
||||
//
|
||||
// Created by WolverinDEV on 30/07/2020.
|
||||
//
|
||||
|
||||
#include <Definitions.h>
|
||||
#include <log/LogUtils.h>
|
||||
|
||||
#include "./snapshot.h"
|
||||
#include "./server.h"
|
||||
#include "./client.h"
|
||||
#include "./channel.h"
|
||||
#include "./music.h"
|
||||
#include "./groups.h"
|
||||
#include "./snapshot_data.h"
|
||||
|
||||
using namespace ts::server;
|
||||
|
||||
bool snapshots::parse_snapshot(snapshot_data &result, std::string &error, ServerId server_id, const command_parser &data) {
|
||||
if(data.bulk(0).has_key("version")) {
|
||||
return snapshots::parse_snapshot_ts3(result, error, server_id, data);
|
||||
} else if(data.bulk(1).has_key("snapshot_version")) {
|
||||
/* teaspeak snapshot */
|
||||
return snapshots::parse_snapshot_teaspeak(result, error, server_id, data);
|
||||
} else {
|
||||
/* old TS3 snapshot format */
|
||||
return snapshots::parse_snapshot_ts3(result, error, server_id, data);
|
||||
}
|
||||
}
|
||||
|
||||
bool snapshots::parse_snapshot_teaspeak(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
||||
if(!data.bulk(1).has_key("snapshot_version")) {
|
||||
error = "Missing snapshot version";
|
||||
return false;
|
||||
}
|
||||
auto version = data.bulk(1).value_as<snapshots::version_t>("snapshot_version");
|
||||
auto hash = data.bulk(0).value("hash");
|
||||
|
||||
if(version < 1) {
|
||||
error = "snapshot version too old";
|
||||
return false;
|
||||
} else if(version > 2) {
|
||||
error = "snapshot version is too new";
|
||||
return false;
|
||||
}
|
||||
|
||||
/* the actual snapshot begins at index 2 */
|
||||
return snapshots::parse_snapshot_raw(result, error, server_id, data, hash, 2, type::TEASPEAK, version);
|
||||
}
|
||||
|
||||
bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
||||
snapshots::version_t version{0};
|
||||
if(data.bulk(0).has_key("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")) {
|
||||
error = "TeaSpeak dosn't support encrypted snapshots yet";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(version == 0) {
|
||||
return snapshots::parse_snapshot_raw(result, error, server_id, data, hash, 1, type::TEAMSPEAK, version);
|
||||
} else if(version == 1) {
|
||||
error = "version 1 is an invalid version";
|
||||
return false;
|
||||
} else if(version == 2) {
|
||||
/* compressed data */
|
||||
error = "version 2 isn't currently supported";
|
||||
return false;
|
||||
} else if(version == 3) {
|
||||
error = "version 3 isn't currently supported";
|
||||
return false;
|
||||
} else {
|
||||
error = "snapshots with version 1-3 are currently supported";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
//TODO: Verify hash
|
||||
|
||||
/* all snapshots start with the virtual server properties */
|
||||
{
|
||||
snapshots::server_parser parser{type, version, command};
|
||||
if(!parser.parse(error, snapshot_data.parsed_server, command_offset))
|
||||
return false;
|
||||
}
|
||||
|
||||
/* afterwards all channels */
|
||||
{
|
||||
snapshots::channel_parser parser{type, version, command};
|
||||
auto data = command.bulk(command_offset);
|
||||
if(!data.has_key("begin_channels")) {
|
||||
error = "missing begin channels token at " + std::to_string(data.command_character_index());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto end_bulk = command.next_bulk_containing("end_channels", command_offset);
|
||||
if(!end_bulk.has_value()) {
|
||||
error = "missing end channels token";
|
||||
return false;
|
||||
} else if(*end_bulk == command_offset) {
|
||||
error = "snapshot contains no channels";
|
||||
return false;
|
||||
}
|
||||
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")) {
|
||||
auto& entry = snapshot_data.parsed_channels.emplace_back();
|
||||
if(!parser.parse(error, entry, command_offset))
|
||||
return false;
|
||||
}
|
||||
command_offset++; /* the "end_channels" token */
|
||||
}
|
||||
|
||||
/* after channels all clients */
|
||||
{
|
||||
snapshots::client_parser parser{type, version, command};
|
||||
auto data = command.bulk(command_offset);
|
||||
if(!data.has_key("begin_clients")) {
|
||||
error = "missing begin clients token at " + std::to_string(data.command_character_index());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto end_bulk = command.next_bulk_containing("end_clients", command_offset);
|
||||
if(!end_bulk.has_value()) {
|
||||
error = "missing end clients token";
|
||||
return false;
|
||||
}
|
||||
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")) {
|
||||
auto& entry = snapshot_data.parsed_clients.emplace_back();
|
||||
if(!parser.parse(error, entry, command_offset))
|
||||
return false;
|
||||
}
|
||||
command_offset++; /* the "end_clients" token */
|
||||
}
|
||||
|
||||
/* permissions */
|
||||
{
|
||||
bool server_groups_parsed{false},
|
||||
channel_groups_parsed{false},
|
||||
client_permissions_parsed{false},
|
||||
channel_permissions_parsed{false},
|
||||
client_channel_permissions_parsed{false};
|
||||
|
||||
if(!command.bulk(command_offset++).has_key("begin_permissions")) {
|
||||
error = "missing begin permissions key";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::relation_parser relation_parser{type, version, command};
|
||||
while(!command.bulk(command_offset).has_key("end_permissions")) {
|
||||
if(command.bulk(command_offset).has_key("server_groups")) {
|
||||
if(std::exchange(server_groups_parsed, true)) {
|
||||
error = "duplicated server group list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::group_parser group_parser{type, version, command, "id", permission::teamspeak::GroupType::SERVER};
|
||||
|
||||
/* parse all groups */
|
||||
while(!command.bulk(command_offset).has_key("end_groups")){
|
||||
auto& group = snapshot_data.parsed_server_groups.emplace_back();
|
||||
if(!group_parser.parse(error, group, command_offset)) /* will consume the end group token */
|
||||
return false;
|
||||
command_offset++; /* for the "end_group" token */
|
||||
}
|
||||
command_offset++; /* for the "end_groups" token */
|
||||
|
||||
/* parse relations */
|
||||
if(!relation_parser.parse(error, snapshot_data.parsed_server_group_relations, command_offset))
|
||||
return false;
|
||||
command_offset++; /* for the "end_relations" token */
|
||||
|
||||
if(snapshot_data.parsed_server_group_relations.size() > 1) {
|
||||
error = "all group relations should be for channel id 0 but received more than one different channel.";
|
||||
return false;
|
||||
} else if(!snapshot_data.parsed_server_group_relations.empty() && snapshot_data.parsed_server_group_relations.begin()->first != 0) {
|
||||
error = "all group relations should be for channel id 0 but received it for " + std::to_string(snapshot_data.parsed_server_group_relations.begin()->first);
|
||||
return false;
|
||||
}
|
||||
} else if(command.bulk(command_offset).has_key("channel_groups")) {
|
||||
if(std::exchange(channel_groups_parsed, true)) {
|
||||
error = "duplicated channel group list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::group_parser group_parser{type, version, command, "id", permission::teamspeak::GroupType::CHANNEL};
|
||||
|
||||
/* parse all groups */
|
||||
while(!command.bulk(command_offset).has_key("end_groups")){
|
||||
auto& group = snapshot_data.parsed_channel_groups.emplace_back();
|
||||
if(!group_parser.parse(error, group, command_offset))
|
||||
return false;
|
||||
command_offset++; /* for the "end_group" token */
|
||||
}
|
||||
command_offset++; /* for the "end_groups" token */
|
||||
|
||||
/* parse relations */
|
||||
if(!relation_parser.parse(error, snapshot_data.parsed_channel_group_relations, command_offset))
|
||||
return false;
|
||||
command_offset++; /* for the "end_relations" token */
|
||||
} else if(command.bulk(command_offset).has_key("client_flat")) {
|
||||
/* client permissions */
|
||||
if(std::exchange(client_permissions_parsed, true)) {
|
||||
error = "duplicated client permissions list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CLIENT};
|
||||
if(!flat_parser.parse(error, snapshot_data.client_permissions, command_offset))
|
||||
return false;
|
||||
command_offset++; /* for the "end_flat" token */
|
||||
} else if(command.bulk(command_offset).has_key("channel_flat")) {
|
||||
/* channel permissions */
|
||||
if(std::exchange(channel_permissions_parsed, true)) {
|
||||
error = "duplicated channel permissions list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CHANNEL};
|
||||
if(!flat_parser.parse(error, snapshot_data.channel_permissions, command_offset))
|
||||
return false;
|
||||
|
||||
command_offset++; /* for the "end_flat" token */
|
||||
} else if(command.bulk(command_offset).has_key("channel_client_flat")) {
|
||||
/* channel client permissions */
|
||||
if(std::exchange(client_channel_permissions_parsed, true)) {
|
||||
error = "duplicated client channel permissions list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CLIENT};
|
||||
if(!flat_parser.parse(error, snapshot_data.client_channel_permissions, command_offset))
|
||||
return false;
|
||||
|
||||
command_offset++; /* for the "end_flat" token */
|
||||
} else {
|
||||
command_offset++;
|
||||
}
|
||||
}
|
||||
|
||||
/* check if everything has been parsed */
|
||||
{
|
||||
if(!server_groups_parsed) {
|
||||
error = "missing server groups";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!channel_groups_parsed) {
|
||||
error = "missing channel groups";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!client_permissions_parsed) {
|
||||
error = "missing client permissions";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!channel_permissions_parsed) {
|
||||
error = "missing channel permissions";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!client_channel_permissions_parsed) {
|
||||
error = "missing client channel permissions";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <Definitions.h>
|
||||
#include <query/command3.h>
|
||||
|
||||
namespace ts::server::snapshots {
|
||||
enum struct type {
|
||||
TEAMSPEAK,
|
||||
TEASPEAK
|
||||
TEASPEAK,
|
||||
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
typedef int32_t version_t;
|
||||
@ -43,4 +46,14 @@ namespace ts::server::snapshots {
|
||||
const type type_;
|
||||
const version_t version_;
|
||||
};
|
||||
|
||||
struct snapshot_data;
|
||||
|
||||
extern bool parse_snapshot(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const ts::command_parser& /* command */);
|
||||
|
||||
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_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 */);
|
||||
|
||||
}
|
32
server/src/snapshots/snapshot_data.h
Normal file
32
server/src/snapshots/snapshot_data.h
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// Created by WolverinDEV on 30/07/2020.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "./snapshot.h"
|
||||
|
||||
namespace ts::server::snapshots {
|
||||
struct snapshot_data {
|
||||
snapshots::type type{snapshots::type::UNKNOWN};
|
||||
version_t version{-1};
|
||||
|
||||
snapshots::server_entry parsed_server{};
|
||||
|
||||
std::vector<snapshots::channel_entry> parsed_channels{};
|
||||
std::vector<snapshots::client_entry> parsed_clients{};
|
||||
|
||||
std::vector<snapshots::group_entry> parsed_server_groups{};
|
||||
snapshots::group_relations parsed_server_group_relations{};
|
||||
|
||||
std::vector<snapshots::group_entry> parsed_channel_groups{};
|
||||
snapshots::group_relations parsed_channel_group_relations{};
|
||||
|
||||
std::deque<snapshots::permissions_flat_entry> client_permissions{};
|
||||
std::deque<snapshots::permissions_flat_entry> channel_permissions{};
|
||||
std::deque<snapshots::permissions_flat_entry> client_channel_permissions{};
|
||||
|
||||
std::deque<snapshots::music_bot_entry> music_bots{};
|
||||
std::deque<snapshots::playlist_entry> playlists{};
|
||||
std::deque<snapshots::playlist_song_entry> playlist_songs{};
|
||||
};
|
||||
}
|
2
shared
2
shared
@ -1 +1 @@
|
||||
Subproject commit 1c6482262f183f99dae9f230d5fcfe632f85fe4a
|
||||
Subproject commit a6343968450d4e626bcf99ec622b4a325bf8c4ba
|
Loading…
Reference in New Issue
Block a user