First draft of the new snapshot system and database changes

This commit is contained in:
WolverinDEV 2020-07-30 20:25:45 +02:00
parent c60119af00
commit 72abd7e20e
20 changed files with 1558 additions and 961 deletions

@ -1 +1 @@
Subproject commit d75ecb67041c3b9958d75848c253e35774d36578
Subproject commit 0dc8e28125f9727f2dffef4b6f93b397099db1f0

View File

@ -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

View File

@ -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;

View File

@ -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;
};
}

View File

@ -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");
}

View File

@ -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 */);
};
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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 = &notify;
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};
}

View File

@ -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)))

View File

@ -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]";
}
}

View File

@ -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};
}

View File

@ -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;
}

View File

@ -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;
}

View 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;
}

View 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;
};
}

View 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;
}

View File

@ -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 */);
}

View 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

@ -1 +1 @@
Subproject commit 1c6482262f183f99dae9f230d5fcfe632f85fe4a
Subproject commit a6343968450d4e626bcf99ec622b4a325bf8c4ba