Teaspeak-Server/server/src/DatabaseHelper.cpp

1361 lines
61 KiB
C++

#include <algorithm>
#include <cstring>
#include <log/LogUtils.h>
#include <sql/SqlQuery.h>
#include <src/client/DataClient.h>
#include <misc/std_unique_ptr.h>
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::permission;
//#define DISABLE_CACHING
DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {}
DatabaseHelper::~DatabaseHelper() {
for(const auto& elm : cachedPermissionManagers)
delete elm;
cachedPermissionManagers.clear();
}
void DatabaseHelper::tick() {
{
threads::MutexLock l(this->permManagerLock);
auto cpy = this->cachedPermissionManagers;
for(const auto& mgr : cpy){
//if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) //TODO remove instand delete!
mgr->ownLock.reset();
if(mgr->manager.expired()){
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), mgr));
delete mgr;
}
}
}
{
threads::MutexLock l(this->propsLock);
auto pcpy = this->cachedProperties;
for(const auto& mgr : pcpy){
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5))
mgr->ownLock.reset();
if(mgr->properties.expired()) {
this->cachedProperties.erase(std::find(this->cachedProperties.begin(), this->cachedProperties.end(), mgr));
delete mgr;
}
}
}
}
int collectData(deque<shared_ptr<ClientDatabaseInfo>>* list, int length, char** values, char** columns){
shared_ptr<ClientDatabaseInfo> entry = std::make_shared<ClientDatabaseInfo>();
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("Invalid db key for manager data. Key: " + string(columns[index]));
list->push_back(entry);
return 0;
}
#define MAX_QUERY 32
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<TSServer>& server, const std::deque<ClientDbId>& list) {
if(list.empty()) return {};
deque<shared_ptr<ClientDatabaseInfo>> result;
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("[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));
auto res = this->queryDatabaseInfo(server, sub);
result.insert(result.end(), res.begin(), res.end());
sub.clear();
} while(!list.empty());
}
return result;
}
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfoByUid(const std::shared_ptr<TSServer> &server, std::deque<std::string> list) {
if(list.empty()) return {};
deque<shared_ptr<ClientDatabaseInfo>> result;
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("[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 res = this->queryDatabaseInfoByUid(server, sub);
result.insert(result.end(), res.begin(), res.end());
sub.clear();
} while(!list.empty());
}
return result;
}
bool DatabaseHelper::validClientDatabaseId(const std::shared_ptr<TSServer>& server, ClientDbId cldbid) { return cldbid > 0; } //TODO here check
void DatabaseHelper::deleteClient(const std::shared_ptr<TSServer>& server, ClientDbId cldbid) {
ServerId sid = static_cast<ServerId>(server ? server->getServerId() : 0);
{
lock_guard<threads::Mutex> lock(permManagerLock);
for(auto permMgr : this->cachedPermissionManagers)
if(permMgr->cldbid == cldbid && permMgr->sid == sid) {
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
delete permMgr;
break;
}
}
//TODO remove from props cache?
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();
//TODO delete letters
//TODO delete query
//TODO delete complains
}
struct PermissionArguments {
sql::command& command;
PermissionManager* manager;
TSServer* server;
inline int serverId() { return this->server ? this->server->getServerId() : 0; }
};
inline sql::result load_permissions(const std::shared_ptr<TSServer>& server, PermissionManager* manager, sql::command& command) {
auto start = system_clock::now();
auto data = PermissionArguments{command, manager, server.get()};
return command.query([](PermissionArguments* data, int length, char** values, char** names){
permission::PermissionType key = permission::PermissionType::undefined;
permission::PermissionValue value = 0, granted = 0;
bool negated = false, skipped = false;
std::shared_ptr<BasicChannel> channel;
int index;
try {
for(index = 0; index < length; index++) {
if(strcmp(names[index], "permId") == 0) {
key = permission::resolvePermissionData(values[index])->type;
if(key == permission::unknown){
debugMessage(data->serverId(), "[SQL] Permission entry contains invalid permission type! Type: {} Command: {}", values[index], data->command.sqlCommand());
return 0;
}
if(key == permission::undefined){
debugMessage(data->serverId(), "[SQL] Permission entry contains undefined permission type! Type: {} Command: {}", values[index], data->command.sqlCommand());
return 0;
}
} else if(strcmp(names[index], "channelId") == 0) {
auto channelId = stoull(values[index]);
if(channelId > 0) {
if(!data->server)
logError(data->serverId(), "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", data->command.sqlCommand(), values[index]);
else {
channel = data->server->getChannelTree()->findChannel(channelId);
if(!channel)
logError(data->serverId(), "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", data->command.sqlCommand(), values[index]);
}
}
} else if(strcmp(names[index], "value") == 0) {
value = stoi(values[index]);
} else if(strcmp(names[index], "grant") == 0) {
granted = stoi(values[index]);
} else if(strcmp(names[index], "flag_skip") == 0)
skipped = strcmp(values[index], "1") == 0;
else if(strcmp(names[index], "flag_negate") == 0)
negated = strcmp(values[index], "1") == 0;
}
} catch(std::exception& ex) {
logError(data->serverId(), "[SQL] Cant load permissions! Failed to parse value! Command: {} Message: {} Key: {} Value: {}", data->command.sqlCommand(), ex.what(),names[index], values[index]);
return 0;
}
if(key == permission::undefined) {
debugMessage(data->serverId(), "[SQL] Permission entry misses permission type! Command: {}", data->command.sqlCommand());
return 0;
}
auto permission = data->manager->registerPermission(key, value, channel);
permission->granted = granted;
permission->value = value;
permission->dbReference = true;
permission->flag_negate = negated;
permission->flag_skip = skipped;
return 0;
}, &data);
auto end = system_clock::now();
auto time = end - start;
logTrace(server ? server->getServerId() : 0, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
}
inline sql::result load_permissions_v2(const std::shared_ptr<TSServer>& server, v2::PermissionManager* manager, sql::command& command, bool resolve_channel /* only used for client permissions (client channel permissions) */) {
auto start = system_clock::now();
auto server_id = server ? server->getServerId() : 0;
return command.query([&](int length, char** values, char** names){
permission::PermissionType key = permission::PermissionType::undefined;
permission::PermissionValue value = 0, granted = 0;
bool negated = false, skipped = false;
std::shared_ptr<BasicChannel> channel;
int index;
try {
for(index = 0; index < length; index++) {
if(strcmp(names[index], "permId") == 0) {
key = permission::resolvePermissionData(values[index])->type;
if(key == permission::unknown){
debugMessage(server_id, "[SQL] Permission entry contains invalid permission type! Type: {} Command: {}", values[index], command.sqlCommand());
return 0;
}
if(key == permission::undefined){
debugMessage(server_id, "[SQL] Permission entry contains undefined permission type! Type: {} Command: {}", values[index], command.sqlCommand());
return 0;
}
} else if(strcmp(names[index], "channelId") == 0) {
if(resolve_channel) {
auto channelId = stoull(values[index]);
if(channelId > 0) {
if(!server)
logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
else {
channel = server->getChannelTree()->findChannel(channelId);
if(!channel)
logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
}
}
}
} else if(strcmp(names[index], "value") == 0) {
value = stoi(values[index]);
} else if(strcmp(names[index], "grant") == 0) {
granted = stoi(values[index]);
} else if(strcmp(names[index], "flag_skip") == 0)
skipped = strcmp(values[index], "1") == 0;
else if(strcmp(names[index], "flag_negate") == 0)
negated = strcmp(values[index], "1") == 0;
}
} catch(std::exception& ex) {
logError(server_id, "[SQL] Cant load permissions! Failed to parse value! Command: {} Message: {} Key: {} Value: {}", command.sqlCommand(), ex.what(),names[index], values[index]);
return 0;
}
if(key == permission::undefined) {
debugMessage(server_id, "[SQL] Permission entry misses permission type! Command: {}", command.sqlCommand());
return 0;
}
if(!resolve_channel || !channel)
manager->load_permission(key, {value, granted}, negated, skipped, value != permNotGranted, granted != permNotGranted);
else
manager->load_permission(key, {value, granted}, channel->channelId(), negated, skipped, value != permNotGranted, granted != permNotGranted);
return 0;
});
auto end = system_clock::now();
auto time = end - start;
logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
}
#define UPDATE_COMMAND "UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
#define INSERT_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"
#define DELETE_COMMAND "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<TSServer>& server, ClientDbId cldbid) {
auto server_id = server ? server->getServerId() : 0;
#ifndef DISABLE_CACHING
{
lock_guard<threads::Mutex> lock(permManagerLock);
for(auto permMgr : this->cachedPermissionManagers)
if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)){
auto ptr = permMgr->manager.lock();
if(!ptr){
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
delete permMgr;
break;
}
permMgr->lastAccess = system_clock::now();
return ptr;
}
}
#endif
logTrace(server_id, "[Permission] Loading client permission manager for client {}", cldbid);
auto pMgr = std::make_shared<v2::PermissionManager>();
bool loaded = false;
if(this->use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
entry = entries;
break;
}
}
}
if(entry) {
for(const auto& perm : entry->permissions) {
if(perm->type == permission::SQL_PERM_USER && perm->id == cldbid) {
auto channel = perm->channelId > 0 ? server->getChannelTree()->findChannel(perm->channelId) : nullptr;
if(channel)
pMgr->load_permission(perm->permission->type, {perm->value, perm->grant}, channel->channelId(), perm->flag_negate, perm->flag_skip, perm->value != permNotGranted, perm->grant != permNotGranted);
else
pMgr->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_negate, perm->flag_skip, perm->value != permNotGranted, perm->grant != permNotGranted);
}
}
loaded = true;
}
}
if(!loaded) {
auto command = sql::command(this->sql, "SELECT `permId`, `value`, `channelId`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_USER},
variable{":id", cldbid});
LOG_SQL_CMD(load_permissions_v2(server, pMgr.get(), command, true));
}
#ifndef DISABLE_CACHING
this->permManagerLock.lock();
auto entry = new CachedPermissionManager();
entry->sid = server_id;
entry->manager = pMgr;
entry->ownLock = pMgr;
entry->cldbid = cldbid;
entry->lastAccess = system_clock::now();
this->cachedPermissionManagers.push_back(entry);
this->permManagerLock.unlock();
#endif
return pMgr;
}
void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::TSServer> &server, ts::ClientDbId client_dbid, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
auto permission_data = permission::resolvePermissionData(update.permission);
logTrace(server_id, "[CHANNEL] Updating client permission for client {}: {}. New value: {}. New grant: {}. Query: {}",
client_dbid,
permission_data->name,
update.values.value,
update.values.grant,
query
);
sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0},
variable{":id", client_dbid},
variable{":chId", 0},
variable{":type", permission::SQL_PERM_USER},
variable{":permId", permission_data->name},
variable{":value", update.values.value},
variable{":grant", update.values.grant},
variable{":flag_skip", update.flag_skip},
variable{":flag_negate", update.flag_negate})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future error"});
}
}
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPermissions(const std::shared_ptr<TSServer>& server, ts::GroupId group_id) {
auto result = std::make_shared<v2::PermissionManager>();
if(this->use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
entry = entries;
break;
}
}
}
if(entry) {
for(const auto& perm : entry->permissions) {
if(perm->type == permission::SQL_PERM_GROUP && perm->id == group_id) {
result->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_negate, perm->flag_skip, perm->value != permNotGranted, perm->grant != permNotGranted);
}
}
return result;
}
}
//7931
auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id});
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
return result;
}
void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::TSServer> &server, ts::GroupId group_id, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
auto permission_data = permission::resolvePermissionData(update.permission);
logTrace(server_id, "[CHANNEL] Updating group permission for group {}: {}. New value: {}. New grant: {}. Query: {}",
group_id,
permission_data->name,
update.values.value,
update.values.grant,
query
);
sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0},
variable{":id", group_id},
variable{":chId", 0},
variable{":type", permission::SQL_PERM_GROUP},
variable{":permId", permission_data->name},
variable{":value", update.values.value},
variable{":grant", update.values.grant},
variable{":flag_skip", update.flag_skip},
variable{":flag_negate", update.flag_negate})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future error"});
}
}
std::shared_ptr<permission::PermissionManager> DatabaseHelper::loadPlaylistPermissions(const std::shared_ptr<ts::server::TSServer> &server, ts::PlaylistId playlist_id) {
shared_ptr<PermissionManager> result;
if(this->use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
entry = entries;
break;
}
}
}
if(entry) {
result = std::make_shared<PermissionManager>();
for(const auto& perm : entry->permissions) {
if(perm->type == permission::SQL_PERM_PLAYLIST && perm->id == playlist_id) {
auto permission = result->registerPermission(perm->permission, perm->value, server->getChannelTree()->findChannel(perm->channelId));
permission->granted = perm->grant;
permission->value = perm->value;
permission->dbReference = true;
permission->flag_negate = perm->flag_negate;
permission->flag_skip = perm->flag_skip;
}
}
}
}
if(!result) {
result = std::make_shared<PermissionManager>();
auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_PLAYLIST},
variable{":id", playlist_id});
LOG_SQL_CMD(load_permissions(server, result.get(), command));
}
assert(result);
weak_ptr<TSServer> weak_server = server;
auto server_id = server ? server->getServerId() : 0;
result->registerUpdateHandler([&, weak_server, server_id, playlist_id](std::shared_ptr<Permission> permission) {
auto server = weak_server.lock();
if(!server && server_id != 0) {
logError(server_id, "Tried to update a playlist permission of a expired server!");
return;
}
std::string query;
/*
if(permission->deleted){
query = "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `id` = :id AND `type`= :type AND `permId`= :permId AND `channelId` = :chId";
} else
*/
if(permission->dbReference){
query = UPDATE_COMMAND;
} else {
permission->dbReference = true;
query = INSERT_COMMAND;
}
logTrace(server ? server->getServerId() : 0, "[SQL] Executing group permission command: {}", query);
sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0},
variable{":id", playlist_id},
variable{":type", permission::SQL_PERM_PLAYLIST},
variable{":chId", permission->channelId()},
variable{":permId", permission->type->name},
variable{":value", permission->value},
variable{":grant", permission->granted},
variable{":flag_skip", permission->flag_skip},
variable{":flag_negate", permission->flag_negate})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to save playlist permission " + to_string(playlist_id)});
});
return result;
}
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadChannelPermissions(const std::shared_ptr<TSServer>& server, ts::ChannelId channel) {
auto result = std::make_shared<v2::PermissionManager>();
if(this->use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
entry = entries;
break;
}
}
}
if(entry) {
for(const auto& perm : entry->permissions) {
if(perm->type == permission::SQL_PERM_CHANNEL && perm->channelId == channel) {
result->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_negate, perm->flag_skip, perm->value != permNotGranted, perm->grant != permNotGranted);
}
}
return result;
}
}
auto command = sql::command(sql, "SELECT `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `channelId` = :chid AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":chid", channel},
variable{":id", 0},
variable{":type", permission::SQL_PERM_CHANNEL});
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
return result;
}
void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::TSServer> &server, ts::ChannelId channel_id, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
auto permission_data = permission::resolvePermissionData(update.permission);
logTrace(server_id, "[CHANNEL] Updating channel permission for channel {}: {}. New value: {}. New grant: {}. Query: {}",
channel_id,
permission_data->name,
update.values.value,
update.values.grant,
query
);
sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0},
variable{":id", 0},
variable{":chId", channel_id},
variable{":type", permission::SQL_PERM_CHANNEL},
variable{":permId", permission_data->name},
variable{":value", update.values.value},
variable{":grant", update.values.grant},
variable{":flag_skip", update.flag_skip},
variable{":flag_negate", update.flag_negate})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future error"});
}
}
#define DBSAVE_MUSIC_BOT (type == CLIENT_MUSIC ? PROP_DB_SAVE : 0)
void DatabaseHelper::assign_default_properties_client(Properties *properties, ClientType type){
Properties& _properties = *properties;
_properties.register_property_type<property::ClientProperties>();
_properties.register_property_type<property::ConnectionProperties>();
if(type == ClientType::CLIENT_MUSIC){
//_properties.registerProperty("channel_last", 0, PROP_DB_SAVE);
//_properties.registerProperty("bot_owner", 0, PROP_TEMP); //saved in bot table
//_properties.registerProperty("music_volume", 1.f, PROP_DB_SAVE);
//_properties.registerProperty("music_track_id", 0, PROP_CLIENT_VARIABLE | PROP_CLIENT_VIEW_INFO | PROP_NEW);
//_properties.registerProperty("music_player_state", 0, PROP_CLIENT_VARIABLE | PROP_CLIENT_VIEW_INFO | PROP_NEW);
_properties[property::CLIENT_INPUT_HARDWARE] = true;
_properties[property::CLIENT_OUTPUT_HARDWARE] = true;
} else if(type == ClientType::CLIENT_QUERY) {
_properties[property::CLIENT_INPUT_HARDWARE] = true;
_properties[property::CLIENT_OUTPUT_HARDWARE] = true;
}
/*
_properties.registerProperty("client_type", type, PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_type_exact", type, PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("clid", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO | PROP_CLIENT_VARIABLE);
_properties.registerProperty("client_database_id", 0, PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("hwid", "", PROP_PRIVATE | PROP_DB_SAVE);
_properties.registerProperty("connection_client_ip", "undefined", PROP_PRIVATE | PROP_DB_SAVE | PROP_SNAPSHOT);
_properties.registerProperty("client_version", "unknown", PROP_CLIENT_VARIABLE | PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE | PROP_SNAPSHOT);
_properties.registerProperty("client_platform", "unknown", PROP_CLIENT_VARIABLE | PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE | PROP_SNAPSHOT);
_properties.registerProperty("client_login_name", "", PROP_CLIENT_VARIABLE);
_properties.registerProperty("client_created", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE | DBSAVE_MUSIC_BOT | PROP_SNAPSHOT);
_properties.registerProperty("client_lastconnected", 0, PROP_SERVER_BOUND | DBSAVE_MUSIC_BOT | PROP_CLIENT_VARIABLE | PROP_SNAPSHOT);
_properties.registerProperty("client_totalconnections", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE | PROP_CLIENT_VIEW_INFO | PROP_SNAPSHOT);
_properties.registerProperty("client_month_bytes_uploaded", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE);
_properties.registerProperty("client_month_bytes_downloaded", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE);
_properties.registerProperty("client_total_bytes_uploaded", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE);
_properties.registerProperty("client_total_bytes_downloaded", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE);
_properties.registerProperty("connection_server2client_packetloss_keepalive", 0, PROP_SERVER_BOUND);
_properties.registerProperty("connection_server2client_packetloss_control", 0, PROP_SERVER_BOUND);
_properties.registerProperty("connection_server2client_packetloss_speech", 0, PROP_SERVER_BOUND);
_properties.registerProperty("connection_server2client_packetloss_total", 0, PROP_SERVER_BOUND);
_properties.registerProperty("client_unique_identifier", "", PROP_CLIENT_VIEW_INFO | PROP_SNAPSHOT);
_properties.registerProperty("client_nickname", "", PROP_CLIENT_VIEW_INFO | DBSAVE_MUSIC_BOT | PROP_SNAPSHOT);
_properties.registerProperty("client_nickname_phonetic", "", PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_input_muted", false, PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_output_muted", false, PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_outputonly_muted", false, PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_input_hardware", false, PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_output_hardware", false, PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_is_recording", false, PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_meta_data", "", PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); //unused
_properties.registerProperty("client_channel_group_id", "0", PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_servergroups", "0", PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_away", false, PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_away_message", "", PROP_CLIENT_VIEW_INFO);CLIENT_DESCRIPTION
_properties.registerProperty("client_flag_avatar", "", PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE); //Some random uuid. File id or stuff like that?
_properties.registerProperty("client_description", ts::config::messages::defaultClientDescription, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE | PROP_SNAPSHOT);
_properties.registerProperty("client_talk_power", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_talk_request", false, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_talk_request_msg", "", PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_is_talker", false, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_is_priority_speaker", false, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_is_channel_commander", false, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_unread_messages", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_needed_serverquery_view_power", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_icon_id", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE);
_properties.registerProperty("client_country", ts::config::geo::countryFlag, PROP_CLIENT_VIEW_INFO); //Country shortcuts
_properties.registerProperty("client_channel_group_inherited_channel_id", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO);
_properties.registerProperty("client_badges", "", PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE);
_properties.registerProperty("client_teaforum_id", 0, PROP_CLIENT_VIEW_INFO | PROP_NEW);
_properties.registerProperty("client_teaforum_name", "", PROP_CLIENT_VIEW_INFO | PROP_NEW);
//Client default channel settings
_properties.registerProperty("client_default_channel", "", PROP_PRIVATE);
_properties.registerProperty("client_default_channel_password", "", PROP_PRIVATE);
*/
}
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::shared_ptr<DataClient> cl) {
cl->loadDataForCurrentServer();
if(cl->getClientDatabaseId() == 0){ //Client does not exist
ClientDbId cldbid = 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;
}, &cldbid);
auto pf = LOG_SQL_CMD;
pf(res);
if(!res) return false;
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(cldbid == 0){ //Completly new user
res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([](ClientDbId* ptr, int length, char** values, char** names){
*ptr = static_cast<ClientDbId>(stoll(values[0]));
return 0;
}, &cldbid);
pf(res);
if(!res) return false;
cldbid += 1;
res = insertTemplate.command().values(variable{":serverId", 0}, variable{":cldbid", cldbid}).execute(); //Insert global
pf(res);
if(!res) return false;
debugMessage(id, "Having new instance user on server {}. (Database ID: {} Name: {})", id, cldbid, cl->getDisplayName());
} else {
debugMessage(id, "Having new server user on server {}. (Database ID: {} Name: {})", id, cldbid, cl->getDisplayName());
}
if(id != 0){ //Else already inserted
res = insertTemplate.command().values(variable{":serverId", id}, variable{":cldbid", cldbid}).execute();
pf(res);
if(!res) return false;
}
return assignDatabaseId(sql, id, cl);
}
logTrace(id, "Loaded client from database. Database id: {} Unique id: {}", cl->getClientDatabaseId(), cl->getUid());
return true;
}
inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEntry>>& properties, sql::command& command) {
auto start = system_clock::now();
auto result = command.query([&](int length, string* values, string* names) {
string key, value;
property::PropertyType type = property::PROP_TYPE_UNKNOWN;
for(int index = 0; index < length; index++) {
try {
if(names[index] == "key") key = values[index];
else if(names[index] == "value") value = values[index];
else if(names[index] == "type") type = (property::PropertyType) stoll(values[index]);
} catch(const std::exception& ex) {
logError(sid, "Failed to load parse property \"{}\". key: {}, value: {}, message: {}", key,names[index],values[index],ex.what());
return 0;
}
}
const auto &info = property::impl::info_key(type, key);
if(info->name == "undefined") {
logError(sid, "Found unknown property in database! ({})", key);
return 0;
}
/*
auto prop = properties->operator[](info);
prop = value;
prop.setModified(true);
prop.setDbReference(true);
*/
auto data = make_unique<FastPropertyEntry>();
data->value = value;
data->type = info;
properties.push_back(move(data));
return 0;
});
auto end = system_clock::now();
auto time = end - start;
logTrace(sid, "[SQL] load_properties(\"{}\") needs {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
return result;
}
std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shared_ptr<ts::server::TSServer>& server) {
auto props = std::make_shared<Properties>();
props->register_property_type<property::VirtualServerProperties>();
(*props)[property::VIRTUALSERVER_HOST] = config::binding::DefaultVoiceHost;
(*props)[property::VIRTUALSERVER_WEB_HOST] = config::binding::DefaultWebHost;
bool loaded = false;
if(use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
entry = entries;
break;
}
}
}
if(entry) {
for(const auto& prop : entry->properties) {
if(prop->type == property::PROP_TYPE_SERVER && prop->id == 0) {
auto p = (*props)[prop->info];
p = prop->value;
p.setModified(true);
p.setDbReference(true);
}
}
loaded = true;
}
}
if(!loaded) {
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", property::PropertyType::PROP_TYPE_SERVER});
deque<unique_ptr<FastPropertyEntry>> property_list;
LOG_SQL_CMD(load_properties(server ? server->getServerId() : 0, property_list, command));
for(const auto& entry : property_list) {
auto prop = props->operator[](entry->type);
prop = entry->value;
prop.setModified(true);
prop.setDbReference(true);
}
}
weak_ptr<TSServer> weak = server;
ServerId serverId = server ? server->getServerId() : 0;
props->registerNotifyHandler([&, serverId, weak](Property& prop){
if((prop.type().flags & property::FLAG_SAVE) == 0) {
prop.setModified(false);
return;
}
auto weak_server = weak.lock();
if(!weak_server && serverId != 0) return;
string sql;
if(prop.hasDbReference())
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key";
else {
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
}
logTrace(serverId, "Updating server property: " + prop.type().name + ". New value: " + prop.value() + ". Query: " + sql);
sql::command(this->sql, sql,
variable{":sid", serverId},
variable{":type", property::PropertyType::PROP_TYPE_SERVER},
variable{":id", 0},
variable{":key", prop.type().name},
variable{":value", prop.value()}
).executeLater().waitAndGetLater(LOG_SQL_CMD, sql::result{1, "future failed"});
});
return props;
}
std::shared_ptr<Properties> DatabaseHelper::loadPlaylistProperties(const std::shared_ptr<ts::server::TSServer>& server, PlaylistId id) {
auto props = std::make_shared<Properties>();
props->register_property_type<property::PlaylistProperties>();
(*props)[property::PLAYLIST_ID] = id;
bool loaded = false;
if(use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
entry = entries;
break;
}
}
}
if(entry) {
for(const auto& prop : entry->properties) {
if(prop->type == property::PROP_TYPE_PLAYLIST && prop->id == id) {
auto p = (*props)[prop->info];
p = prop->value;
p.setModified(true);
p.setDbReference(true);
}
}
loaded = true;
}
}
if(!loaded) {
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", property::PropertyType::PROP_TYPE_PLAYLIST}, variable{":id", id});
deque<unique_ptr<FastPropertyEntry>> property_list;
LOG_SQL_CMD(load_properties(server ? server->getServerId() : 0, property_list, command));
for(const auto& entry : property_list) {
auto prop = props->operator[](entry->type);
prop = entry->value;
prop.setModified(true);
prop.setDbReference(true);
}
}
weak_ptr<TSServer> weak = server;
ServerId serverId = server ? server->getServerId() : 0;
props->registerNotifyHandler([&, serverId, weak, id](Property& prop){
if((prop.type().flags & property::FLAG_SAVE) == 0) {
prop.setModified(false);
return;
}
auto weak_server = weak.lock();
if(!weak_server && serverId != 0) return;
string sql;
if(prop.hasDbReference())
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key";
else {
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
}
logTrace(serverId, "Updating playlist property for {}. Key: {} Value: {}", id, prop.type().name, prop.value());
sql::command(this->sql, sql,
variable{":sid", serverId},
variable{":type", property::PropertyType::PROP_TYPE_PLAYLIST},
variable{":id", id},
variable{":key", prop.type().name},
variable{":value", prop.value()}
).executeLater().waitAndGetLater(LOG_SQL_CMD, sql::result{1, "future failed"});
});
return props;
}
std::shared_ptr<Properties> DatabaseHelper::loadChannelProperties(const shared_ptr<TSServer>& server, ChannelId channel) {
ServerId serverId = server ? server->getServerId() : 0U;
auto props = std::make_shared<Properties>();
props->register_property_type<property::ChannelProperties>();
if(server) {
props->operator[](property::CHANNEL_TOPIC) = server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_TOPIC].value();
props->operator[](property::CHANNEL_DESCRIPTION) = server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_DESCRIPTION].value();
}
bool loaded = false;
if(use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
entry = entries;
break;
}
}
}
if(entry) {
for(const auto& prop : entry->properties) {
if(prop->type == property::PROP_TYPE_CHANNEL && prop->id == channel) {
auto p = (*props)[prop->info];
p = prop->value;
p.setModified(true);
p.setDbReference(true);
}
}
loaded = true;
}
}
if(!loaded) {
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", serverId}, variable{":type", property::PropertyType::PROP_TYPE_CHANNEL}, variable{":id", channel});
deque<unique_ptr<FastPropertyEntry>> property_list;
LOG_SQL_CMD(load_properties(serverId, property_list, command));
for(const auto& entry : property_list) {
auto prop = props->operator[](entry->type);
prop = entry->value;
prop.setModified(true);
prop.setDbReference(true);
}
}
weak_ptr<TSServer> weak = server;
props->registerNotifyHandler([&, weak, serverId, channel](Property& prop){
auto weak_server = weak.lock();
if(!weak_server && serverId != 0)
return;
if((prop.type().flags & property::FLAG_SAVE) == 0)
return;
if(!prop.isModified())
return;
std::string query;
if(prop.type() == property::CHANNEL_PID){
query = "UPDATE `channels` SET `parentId` = :value WHERE `serverId` = :serverId AND `channelId` = :id";
} else if(!prop.hasDbReference()){
query = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
} else {
query = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `id` = :id AND `key` = :key AND `type` = :type";
}
logTrace(serverId, "[CHANNEL] Updating channel property for channel {}: {}. New value: '{}'. Query: {}", channel, prop.type().name, prop.value(), query);
sql::command(this->sql, query,
variable{":serverId", serverId},
variable{":id", channel},
variable{":type", property::PropertyType::PROP_TYPE_CHANNEL},
variable{":key", prop.type().name},
variable{":value", prop.value()}
).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future error"});
prop.setModified(false);
prop.setDbReference(true);
});
return props;
}
std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shared_ptr<TSServer>& server, ClientDbId cldbid, ClientType type) {
auto props = std::make_shared<Properties>();
assign_default_properties_client(props.get(), type);
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;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
entry = entries;
break;
}
}
}
if(entry) {
for(const auto& prop : entry->properties) {
if(prop->id == cldbid && (prop->type == property::PROP_TYPE_CLIENT || prop->type == property::PROP_TYPE_CONNECTION)) {
auto p = (*props)[prop->info];
p = prop->value;
p.setModified(true);
p.setDbReference(true);
}
}
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});
deque<unique_ptr<FastPropertyEntry>> property_list;
LOG_SQL_CMD(load_properties(server ? server->getServerId() : 0, property_list, command));
for(const auto& entry : property_list) {
auto prop = props->operator[](entry->type);
prop = entry->value;
prop.setModified(true);
prop.setDbReference(true);
}
}
weak_ptr<TSServer> weak_server = server;
auto server_id = server ? server->getServerId() : 0;
props->registerNotifyHandler([&, weak_server, server_id, cldbid](Property& prop){ //General save
auto server = weak_server.lock();
if(!server && server_id != 0) {
logError(server_id, "Tried to update client permissions of a expired server!");
return;
}
if(!prop.isModified()) return;
if((prop.type().flags & property::FLAG_SAVE) == 0 && (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0) {
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + prop.type().name + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
return;
}
if(!prop.get_handle()) return;
if(!prop.get_handle()->isSaveEnabled()) return;
if(!prop.hasDbReference() && (prop.default_value() == prop.value())) return; //No changes to default value
prop.setModified(false);
std::string sql;
if(prop.hasDbReference())
sql = "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)";
}
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + prop.type().name + " value: " + prop.value());
sql::command(this->sql, sql,
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", prop.type().type_property},
variable{":id", cldbid},
variable{":key", prop.type().name},
variable{":value", prop.value()}
).executeLater().waitAndGetLater(LOG_SQL_CMD, sql::result{1, "future failed"});
});
props->registerNotifyHandler([&, weak_server, server_id, cldbid](Property& prop){
auto server = weak_server.lock();
if(!server && server_id != 0) {
logError(server_id, "Tried to update client permissions of a expired server!");
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 '" + 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"});
});
return props;
}
void DatabaseHelper::loadStartupCache() {
this->loadStartupPermissionCache();
this->loadStartupPropertyCache();
this->use_startup_cache = true;
}
size_t DatabaseHelper::cacheBinarySize() {
size_t result = 0;
result += sizeof(this->startup_entries);
for(const auto& entry : this->startup_entries) {
result += sizeof(entry);
result += sizeof(*entry.get());
for(const auto& e : entry->permissions) {
result += sizeof(e);
result += sizeof(e.get());
}
for(const auto& e : entry->properties) {
result += sizeof(e);
result += sizeof(e.get());
result += e->value.length();
}
}
return result;
}
void DatabaseHelper::clearStartupCache(ts::ServerId sid) {
if(sid == 0) {
threads::MutexLock lock(this->startup_lock);
this->startup_entries.clear();
this->use_startup_cache = false;
} else {
threads::MutexLock lock(this->startup_lock);
/*
this->startup_entries.erase(std::remove_if(this->startup_entries.begin(), this->startup_entries.end(), [&](const shared_ptr<StartupCacheEntry>& entry) {
return entry->sid == sid;
}), this->startup_entries.end());
*/
}
}
//SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`
//SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`
struct StartupPermissionArgument {
std::shared_ptr<StartupCacheEntry> current_server;
};
void DatabaseHelper::loadStartupPermissionCache() {
StartupPermissionArgument arg;
sql::command(this->sql, "SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`").query([&](StartupPermissionArgument* arg, int length, char** values, char** names) {
auto key = permission::PermissionTypeEntry::unknown;
permission::PermissionValue value = 0, granted = 0;
permission::PermissionSqlType type = SQL_PERM_GROUP;
bool negated = false, skipped = false;
ChannelId channel = 0;
uint64_t id = 0;
ServerId serverId = 0;
int index;
try {
for(index = 0; index < length; index++) {
if(strcmp(names[index], "permId") == 0) {
key = permission::resolvePermissionData(values[index]);
if(key->type == permission::unknown || key->type == permission::undefined) {
debugMessage(0, "[SQL] Permission entry contains invalid permission type! Type: {}", values[index]);
return 0;
}
} else if(strcmp(names[index], "channelId") == 0) {
channel = stoull(values[index]);
} else if(strcmp(names[index], "id") == 0) {
id = stoull(values[index]);
} else if(strcmp(names[index], "value") == 0) {
value = stoi(values[index]);
} else if(strcmp(names[index], "grant") == 0) {
granted = stoi(values[index]);
} else if(strcmp(names[index], "flag_skip") == 0)
skipped = strcmp(values[index], "1") == 0;
else if(strcmp(names[index], "flag_negate") == 0)
negated = strcmp(values[index], "1") == 0;
else if(strcmp(names[index], "serverId") == 0)
serverId = stoll(values[index]);
else if(strcmp(names[index], "type") == 0)
type = static_cast<PermissionSqlType>(stoll(values[index]));
}
} catch(std::exception& ex) {
logError(0, "[SQL] Cant load permissions! Failed to parse value! Message: {}. Key: {}, Value: \"{}\"", ex.what(),names[index],values[index]);
return 0;
}
if(key == permission::PermissionTypeEntry::unknown) {
debugMessage(0, "[SQL] Permission entry misses permission type!");
return 0;
}
if(serverId == 0) return 0;
if(!arg->current_server || arg->current_server->sid != serverId) {
arg->current_server = nullptr;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entry : this->startup_entries) {
if(entry->sid == serverId) {
arg->current_server = entry;
break;
}
}
if(!arg->current_server) {
arg->current_server = make_shared<StartupCacheEntry>();
arg->current_server->sid = serverId;
this->startup_entries.push_back(arg->current_server);
}
}
}
auto entry = make_unique<StartupPermissionEntry>();
entry->permission = key;
entry->type = type;
entry->value = value;
entry->grant = granted;
entry->flag_negate = negated;
entry->flag_skip = skipped;
entry->id = id;
entry->channelId = channel;
arg->current_server->permissions.push_back(std::move(entry));
return 0;
}, &arg);
}
void DatabaseHelper::loadStartupPropertyCache() {
StartupPermissionArgument arg;
sql::command(this->sql, "SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`").query([&](StartupPermissionArgument* arg, int length, char** values, char** names) {
std::string key, value;
property::PropertyType type = property::PROP_TYPE_UNKNOWN;
ServerId serverId = 0;
uint64_t id = 0;
for(int index = 0; index < length; index++) {
try {
if(strcmp(names[index], "key") == 0) key = values[index];
else if(strcmp(names[index], "value") == 0) value = values[index] == nullptr ? "" : values[index];
else if(strcmp(names[index], "type") == 0) type = (property::PropertyType) stoll(values[index]);
else if(strcmp(names[index], "serverId") == 0) serverId = stoll(values[index]);
else if(strcmp(names[index], "id") == 0) id = stoll(values[index]);
} catch(const std::exception& ex) {
logError(0, "[SQL] Cant load property! Failed to parse value! Message: {}. Key: {}, Value: \"{}\"", ex.what(),names[index],values[index]);
return 0;
}
}
auto info = property::impl::info_key(type, key);
if(info == property::PropertyDescription::unknown) {
logError(serverId, "Invalid property ({} | {})", key, type);
return 0;
}
if(serverId == 0) return 0;
if(!arg->current_server || arg->current_server->sid != serverId) {
arg->current_server = nullptr;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entry : this->startup_entries) {
if(entry->sid == serverId) {
arg->current_server = entry;
break;
}
}
if(!arg->current_server) {
arg->current_server = make_shared<StartupCacheEntry>();
arg->current_server->sid = serverId;
this->startup_entries.push_back(arg->current_server);
}
}
}
auto entry = make_unique<StartupPropertyEntry>();
entry->info = info;
entry->value = value;
entry->id = id;
entry->type = type;
arg->current_server->properties.push_back(std::move(entry));
return 0;
}, &arg);
}
bool DatabaseHelper::deleteGroupPermissions(const std::shared_ptr<ts::server::TSServer> &server, ts::GroupId group_id) {
auto command = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id}).execute();
LOG_SQL_CMD(command);
return !!command;
}
bool DatabaseHelper::deleteChannelPermissions(const std::shared_ptr<ts::server::TSServer> &server, ts::ChannelId channel_id) {
auto command = sql::command(sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `channelId` = :chid",
variable{":serverId", server ? server->getServerId() : 0},
variable{":chid", channel_id}).execute();
LOG_SQL_CMD(command);
return !!command;
}
std::deque<std::unique_ptr<FastPropertyEntry>> DatabaseHelper::query_properties(ts::ServerId server_id, ts::property::PropertyType type, uint64_t id) {
deque<unique_ptr<FastPropertyEntry>> result;
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server_id}, variable{":type", type}, variable{":id", id});
LOG_SQL_CMD(load_properties(server_id, result, command));
return result;
}
bool DatabaseHelper::deletePlaylist(const std::shared_ptr<ts::server::TSServer> &server, ts::PlaylistId playlist_id) {
auto server_id = server ? server->getServerId() : (ServerId) 0;
sql::command(this->sql, "DELETE FROM `playlists` WHERE `serverId` = :server_id AND `playlist_id` = :playlist_id",
variable{":server_id", server_id},
variable{":playlist_id", playlist_id}
).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete playlist " + to_string(playlist_id) + " from table `playlists`"});
sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_PLAYLIST},
variable{":id", playlist_id}
).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete playlist permissions for playlist " + to_string(playlist_id)});
sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", property::PROP_TYPE_PLAYLIST},
variable{":id", playlist_id}
).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete playlist properties for playlist " + to_string(playlist_id)});
return true;
}