Teaspeak-Server/server/src/DatabaseHelper.cpp
2020-04-16 14:05:58 +02:00

1214 lines
58 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(LOG_GENERAL, "Invalid db key for manager data. Key: {}", columns[index]);
list->push_back(entry);
return 0;
}
#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 {};
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(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));
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<VirtualServer> &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(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 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<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);
{
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
}
inline sql::result load_permissions_v2(
const std::shared_ptr<VirtualServer>& server,
v2::PermissionManager* manager,
sql::command& command,
bool test_channel, /* only used for client permissions (client channel permissions) */
bool is_channel) {
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 = permNotGranted, granted = permNotGranted;
bool negated = false, skipped = false;
ChannelId channel_id{0};
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) {
channel_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;
}
} 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(channel_id > 0 && test_channel) {
if(!server)
logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
else {
auto channel = server->getChannelTree()->findChannel(channel_id);
if(!channel)
logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
}
}
if(key == permission::undefined) {
debugMessage(server_id, "[SQL] Permission entry misses permission type! Command: {}", command.sqlCommand());
return 0;
}
if(channel_id == 0 || is_channel)
manager->load_permission(key, {value, granted}, skipped, negated, value != permNotGranted, granted != permNotGranted);
else
manager->load_permission(key, {value, granted}, channel_id, skipped, negated, 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<VirtualServer>& 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 permission_manager = 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)
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, channel->channelId(), perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
else
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_skip, perm->flag_negate, 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, permission_manager.get(), command, true, false));
}
#ifndef DISABLE_CACHING
this->permManagerLock.lock();
auto entry = new CachedPermissionManager();
entry->sid = server_id;
entry->manager = permission_manager;
entry->ownLock = permission_manager;
entry->cldbid = cldbid;
entry->lastAccess = system_clock::now();
this->cachedPermissionManagers.push_back(entry);
this->permManagerLock.unlock();
#endif
return permission_manager;
}
void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::VirtualServer> &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);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
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", update.channel_id},
variable{":type", permission::SQL_PERM_USER},
variable{":permId", permission_data->name},
variable{":value", value},
variable{":grant", 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<VirtualServer>& 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_skip, perm->flag_negate, 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, false));
return result;
}
void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::VirtualServer> &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);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
logTrace(server_id, "[CHANNEL] Updating group permission for group {}: {}. New value: {}. New grant: {}. Query: {}",
group_id,
permission_data->name,
value,
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", value},
variable{":grant", 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::loadPlaylistPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::PlaylistId playlist_id) {
shared_ptr<permission::v2::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<permission::v2::PermissionManager>();
for(const auto& perm : entry->permissions) {
if(perm->type == permission::SQL_PERM_PLAYLIST && perm->id == playlist_id) {
if(perm->channelId)
result->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->channelId, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
else
result->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
}
}
}
return result;
}
result = std::make_shared<permission::v2::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_v2(server, result.get(), command, false, false));
return result;
}
void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer> &server, PlaylistId pid, const std::shared_ptr<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);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
logTrace(server_id, "[PLAYLIST] Updating playlist permission for playlist {}: {}. New value: {}. New grant: {}. Query: {}",
pid,
permission_data->name,
value,
grant,
query
);
sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0},
variable{":id", pid},
variable{":chId", update.channel_id},
variable{":type", permission::SQL_PERM_PLAYLIST},
variable{":permId", permission_data->name},
variable{":value", value},
variable{":grant", 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::loadChannelPermissions(const std::shared_ptr<VirtualServer>& 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_skip, perm->flag_negate, 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, true));
return result;
}
void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::VirtualServer> &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 value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
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,
value,
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", value},
variable{":grant", grant},
variable{":flag_skip", update.flag_skip},
variable{":flag_negate", update.flag_negate})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future error"});
}
}
std::shared_ptr<Properties> DatabaseHelper::default_properties_client(std::shared_ptr<Properties> properties, ClientType type){
if(!properties)
properties = make_shared<Properties>();
properties->register_property_type<property::ClientProperties>();
properties->register_property_type<property::ConnectionProperties>();
if(type == ClientType::CLIENT_MUSIC || type == ClientType::CLIENT_QUERY){
(*properties)[property::CLIENT_INPUT_HARDWARE] = true;
(*properties)[property::CLIENT_OUTPUT_HARDWARE] = true;
}
return properties;
}
std::mutex DatabaseHelper::database_id_mutex{};
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, 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;
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;
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);
}
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;
}
return assignDatabaseId(sql, id, cl);
}
logTrace(id, "Loaded client successfully 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::find(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 = std::make_unique<FastPropertyEntry>();
data->type = &info;
data->value = value;
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::VirtualServer>& 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<VirtualServer> 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: " + std::string{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::VirtualServer>& 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<VirtualServer> 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<VirtualServer>& 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<VirtualServer> 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<VirtualServer>& server, ClientDbId cldbid, ClientType type) {
auto props = DatabaseHelper::default_properties_client(nullptr, 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<VirtualServer> weak_server = server;
auto server_id = server ? server->getServerId() : 0;
props->registerNotifyHandler([&, weak_server, server_id, cldbid, type](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 && (type != ClientType::CLIENT_MUSIC || (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0)) {
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + std::string{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: " + std::string{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 '" + 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"});
});
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 = permNotGranted, granted = permNotGranted;
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;
}
}
const auto& info = property::find(type, key);
if(info.is_undefined()) {
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::VirtualServer> &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::VirtualServer> &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::VirtualServer> &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;
}