Teaspeak-Server/server/src/Group.cpp

914 lines
41 KiB
C++

#include <algorithm>
#include <utility>
#include <log/LogUtils.h>
#include <misc/memtracker.h>
#include "Group.h"
#include "VirtualServer.h"
#include "src/client/ConnectedClient.h"
#include "InstanceHandler.h"
#include "src/server/file/FileServer.h"
#include "src/groups/Group.h"
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::permission;
extern InstanceHandler* serverInstance;
Group::Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId groupId) : _target(target), _type(type) {
memtrack::allocated<Group>(this);
this->handle = handle;
this->_properties = new Properties();
this->_properties->register_property_type<property::GroupProperties>();
this->setPermissionManager(make_shared<permission::v2::PermissionRegister>());
this->properties()[property::GROUP_ID] = groupId;
this->properties()[property::GROUP_TYPE] = type;
/*
this->_properties->registerProperty("sgid", groupId, PROP_GROUP_INIT_SERVER);
this->_properties->registerProperty("cgid", groupId, PROP_GROUP_INIT_CHANNEL);
this->_properties->registerProperty("gid", groupId, PROP_PRIVATE_TEMP);
this->_properties->registerProperty("type", (uint8_t) type, PROP_GROUP_INIT);
this->_properties->registerProperty("name", "Undefined group #" + to_string(groupId), PROP_GROUP_INIT | PROP_SNAPSHOT);
this->_properties->registerProperty("sortid", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("savedb", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("namemode", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("iconid", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("default_group", false, PROP_PRIVATE_TEMP);
*/
this->_properties->registerNotifyHandler([&](Property& prop){
if((prop.type().flags & property::FLAG_SAVE) == 0) return;
if(!this->handle) return;
std::string sql;
if(prop.hasDbReference()){
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :gid AND `key` = :key";
} else {
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :gid, :key, :value)";
}
sql::command(this->handle->sql, sql,
variable{":sid", this->handle->getServerId()}, variable{":type", property::PropertyType::PROP_TYPE_GROUP}, variable{":gid", this->groupId()}, variable{":key", prop.type().name}, variable{":value", prop.value()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
});
}
void Group::setPermissionManager(const std::shared_ptr<permission::v2::PermissionRegister> &manager) {
this->_permissions = manager;
this->apply_properties_from_permissions();
}
void Group::apply_properties_from_permissions() {
{
auto permission = this->_permissions->permission_value_flagged(permission::i_icon_id);
this->properties()[property::GROUP_ICONID] = permission.has_value ? permission.value : 0;
}
{
auto permission = this->_permissions->permission_value_flagged(permission::i_group_show_name_in_tree);
this->properties()[property::GROUP_NAMEMODE] = permission.has_value ? permission.value : 0;
}
{
auto permission = this->_permissions->permission_value_flagged(permission::i_group_sort_id);
this->properties()[property::GROUP_SORTID] = permission.has_value ? permission.value : 0;
}
{
auto permission = this->_permissions->permission_value_flagged(permission::b_group_is_permanent);
this->properties()[property::GROUP_SAVEDB] = permission.has_value ? permission.value : 0;
}
}
Group::~Group() {
delete this->_properties;
memtrack::freed<Group>(this);
}
GroupManager::GroupManager(const shared_ptr<VirtualServer> &server, sql::SqlManager *sql, std::shared_ptr<GroupManager> root) : server(server), sql(sql), root(std::move(root)) { }
GroupManager::~GroupManager() {}
bool GroupManager::loadGroupFormDatabase(GroupId id) {
if(id == 0){
this->groups.clear();
auto res = sql::command(this->sql, "SELECT * FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).query(&GroupManager::insertGroupFromDb, this);
LOG_SQL_CMD(res);
return true;
} else {
for(const auto &e : this->groups)
if(e->groupId() == id){
this->groups.erase(find(this->groups.begin(), this->groups.end(), e));
break;
}
auto res = sql::command(this->sql, "SELECT * FROM `groups` WHERE `serverId` = :sid AND `groupId` = :gid",
variable{":sid", this->getServerId()}, variable{":gid", id}).query(&GroupManager::insertGroupFromDb, this);
auto fn = LOG_SQL_CMD;
fn(res);
return res;
}
}
std::vector<std::shared_ptr<Group>> GroupManager::availableGroups(bool root) {
std::vector<std::shared_ptr<Group>> response;
for(const auto& group : this->groups)
response.push_back(group);
if(root && this->root){
auto elm = this->root->availableGroups();
for(const auto& e : elm)
response.push_back(e);
}
return response;
}
std::vector<std::shared_ptr<Group>> GroupManager::availableServerGroups(bool root){
std::vector<std::shared_ptr<Group>> response;
for(const auto& group : this->groups)
if(group->target() == GroupTarget::GROUPTARGET_SERVER)
response.push_back(group);
if(root && this->root){
auto elm = this->root->availableServerGroups();
for(const auto& e : elm)
response.push_back(e);
}
return response;
}
std::vector<std::shared_ptr<Group>> GroupManager::availableChannelGroups(bool root) {
std::vector<std::shared_ptr<Group>> response;
for(const auto& group : this->groups)
if(group->target() == GroupTarget::GROUPTARGET_CHANNEL)
response.push_back(group);
if(root && this->root){
auto elm = this->root->availableChannelGroups(true);
for(const auto& e : elm)
response.push_back(e);
}
return response;
}
int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
GroupId groupId = 0;
auto target = (GroupTarget) 0xff;
auto type = (GroupType) 0xff;
std::string targetName;
for(int index = 0; index < count; index++){
if(strcmp(column[index], "target") == 0)
target = (GroupTarget) stoll(values[index]);
else if(strcmp(column[index], "type") == 0)
type = (GroupType) stoll(values[index]);
else if(strcmp(column[index], "groupId") == 0)
groupId = (GroupType) stoll(values[index]);
else if(strcmp(column[index], "displayName") == 0)
targetName = values[index];
else if(strcmp(column[index], "serverId") == 0);
else cerr << "Invalid group table row " << column[index] << endl;
}
if((size_t) groupId == 0 || (size_t) target == 0xff || (size_t) type == 0xff || targetName.empty()) {
logCritical(this->getServerId(), "Found invalid group ad database! (GroupId " + to_string(groupId) + ", Target " + to_string(target) + ", Type " + to_string(type) + ", Name '" + targetName + "')");
return 0;
}
/*
assert(groupId != 0);
assert(target != 0xff);
assert(type != 0xff);
assert(!targetName.empty());
*/
shared_ptr<Group> group = std::make_shared<Group>(this, target, type, groupId);
/*
auto res = sql::command(this->sql, "SELECT `key`, `value` FROM `properties` WHERE `serverId` = :sid AND `type` = :type AND `id` = :gid",
variable{":sid", this->getServerId()}, variable{":type", property::PROP_TYPE_GROUP}, variable{":gid", group->groupId()}).query([this](Group* g, int length, char** values, char** columns){
string key, value;
for(int index = 0; index < length; index++)
if(strcmp(columns[index], "key") == 0)
key = values[index];
else if(strcmp(columns[index], "value") == 0)
value = values[index];
auto info = property::info<property::GroupProperties>(key);
if(info == property::GROUP_UNDEFINED) {
logError(this->getServerId(), "Invalid property for group: " + key);
return 0;
}
auto prop = g->properties()[info.property];
prop.setDbReference(true);
prop.value(value, false);
return 0;
}, group.get());
auto print = LOG_SQL_CMD;
print(res);
*/
//FIXME load group properties view database helper (or drop it full because no saved properties)
group->properties()[property::GROUP_NAME] = targetName;
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock() ? this->server.lock()->getServerId() : 0, group->groupId(), (uint8_t) -1));
debugMessage(this->getServerId(), "Push back group -> " + to_string(group->groupId()) + " - " + group->name());
this->groups.push_back(group);
auto iconId = (IconId) group->icon_id();
if(iconId != 0 && serverInstance && serverInstance->getFileServer() && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) {
logMessage(this->getServerId(), "[FILE] Missing group icon (" + to_string(iconId) + ").");
if(config::server::delete_missing_icon_permissions) group->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing);
}
return 0;
}
void GroupManager::handleChannelDeleted(const ChannelId& channel_id) {
unique_lock cache_lock(this->cacheLock);
auto cached_clients = std::vector<shared_ptr<CachedClient>>{this->cachedClients.begin(), this->cachedClients.end()};
cache_lock.unlock();
for(auto& entry : cached_clients) {
lock_guard entry_lock(entry->lock);
entry->channel_groups.erase(channel_id);
}
}
bool GroupManager::isLocalGroup(std::shared_ptr<Group> gr) {
return std::find(this->groups.begin(), this->groups.end(), gr) != this->groups.end();
}
std::shared_ptr<Group> GroupManager::defaultGroup(GroupTarget type, bool enforce_property) {
threads::MutexLock lock(this->cacheLock);
if(this->groups.empty()) return nullptr;
auto server = this->server.lock();
auto id =
server ?
server->properties()[type == GroupTarget::GROUPTARGET_SERVER ? property::VIRTUALSERVER_DEFAULT_SERVER_GROUP : property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_save<GroupId>() :
serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save<GroupId>();
auto group = this->findGroupLocal(id);
if(group || enforce_property) return group;
for(auto elm : this->groups)
if(elm->target() == type)
return elm;
return nullptr; //Worst case!
}
std::shared_ptr<Group> GroupManager::findGroup(GroupId groupId) {
auto result = this->findGroupLocal(groupId);
if(!result && this->root) result = this->root->findGroup(groupId);
return result;
}
std::shared_ptr<Group> GroupManager::findGroupLocal(GroupId groupId) {
for(const auto& elm : this->groups)
if(elm->groupId() == groupId) return elm;
return nullptr;
}
std::vector<std::shared_ptr<Group>> GroupManager::findGroup(GroupTarget target, std::string name) {
vector<shared_ptr<Group>> res;
for(const auto &elm : this->groups)
if(elm->name() == name && elm->target() == target) res.push_back(elm);
if(this->root) {
auto r = root->findGroup(target, name);
for(const auto &e : r) res.push_back(e);
}
return res;
}
ServerId GroupManager::getServerId() {
auto l = this->server.lock();
return l ? l->getServerId() : 0;
}
std::shared_ptr<Group> GroupManager::createGroup(GroupTarget target, GroupType type, std::string name) {
if(type != GROUP_TYPE_NORMAL && this->root) return root->createGroup(target, type, name);
auto rawId = generateGroupId(this->sql);
if(rawId <= 0) {
logError(this->getServerId(), "Could not create a new group! ({})", "Could not generate group id");
return nullptr;
}
auto groupId = (GroupId) rawId;
auto res = sql::command(this->sql, "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", this->getServerId()}, variable{":gid", groupId}, variable{":target", target}, variable{":type", type}, variable{":name", name}).execute();
auto print = LOG_SQL_CMD;
print(res);
if(!res) return nullptr;
std::shared_ptr<Group> group = std::make_shared<Group>(this, target, type, groupId);
group->properties()[property::GROUP_NAME] = name;
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock() ? this->server.lock()->getServerId() : 0, group->groupId(), (uint8_t) -1));
this->groups.push_back(group);
return group;
}
GroupId GroupManager::copyGroup(std::shared_ptr<Group> group, GroupType type, std::string name, ServerId targetServerId) {
auto group_server = group->handle->getServerId();
auto groupId = generateGroupId(this->sql);
auto print = LOG_SQL_CMD;
auto res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":target", groupId}, variable{":ssid", group_server}, variable{":tsid", targetServerId}, variable{":source", group->groupId()}, variable{":type", SQL_PERM_GROUP}).execute();
print(res);
//Properties not used currently
res = sql::command(this->sql, "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, `key`, `value` FROM `properties` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":target", groupId}, variable{":ssid", group_server}, variable{":tsid", targetServerId}, variable{":source", group->groupId()}, variable{":type", property::PropertyType::PROP_TYPE_GROUP}).execute();
print(res);
res = sql::command(this->sql, "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", targetServerId}, variable{":gid", groupId}, variable{":target", group->target()}, variable{":type", type}, variable{":name", name}).execute();
print(res);
if(targetServerId == (this->getServerId()))
this->loadGroupFormDatabase(groupId);
else if(this->root)
this->root->loadGroupFormDatabase(groupId);
return groupId;
}
bool GroupManager::copyGroupPermissions(const shared_ptr<Group> &source, const shared_ptr<Group> &target) {
auto targetServer = target->handle->getServerId();
auto sourceServer = source->handle->getServerId();
auto res = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":id", target->groupId()}).execute();
LOG_SQL_CMD(res);
res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":ssid", sourceServer}, variable{":tsid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":source", source->groupId()}, variable{":target", target->groupId()}).execute();
target->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(target->handle->server.lock() ? target->handle->server.lock()->getServerId() : 0, target->groupId(), (uint8_t) -1));
LOG_SQL_CMD(res);
return true;
}
bool GroupManager::reloadGroupPermissions(std::shared_ptr<Group> group) {
if(!isLocalGroup(group)){
if(this->root) return this->root->reloadGroupPermissions(group);
return false;
}
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock() ? this->server.lock()->getServerId() : 0, group->groupId(), (uint8_t) -1));
return true;
}
bool GroupManager::renameGroup(std::shared_ptr<Group> group, std::string name) {
if(!isLocalGroup(group)){
if(this->root) return this->root->renameGroup(group, name);
return false;
}
// CREATE_TABLE("groups", "`serverId` INT NOT NULL, `groupId` INTEGER, `target` INT, `type` INT, `displayName` TEXT");
group->properties()[property::GROUP_NAME] = name;
sql::command(this->sql, "UPDATE `groups` SET `displayName` = :name WHERE `serverId` = :sid AND `groupId` = :gid AND `target` = :target",
variable{":name", name}, variable{":gid", group->groupId()}, variable{":target", group->target()}, variable{":sid", this->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed"});
return true;
}
bool GroupManager::deleteAllGroups() {
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type", variable{":sid", this->getServerId()}, variable{":type", SQL_PERM_GROUP}).execute());
{
lock_guard cache_lock(this->cacheLock);
for(const auto& entry : this->cachedClients) {
lock_guard entry_lock(entry->lock);
entry->server_groups.clear();
entry->channel_groups.clear();
}
}
this->groups.clear();
return true;
}
bool GroupManager::deleteGroup(std::shared_ptr<Group> group) {
if(!isLocalGroup(group)){
if(this->root) return this->root->deleteGroup(group);
return false;
}
this->groups.erase(std::find(this->groups.begin(), this->groups.end(), group));
/* erase the group out of our cache */
{
lock_guard cache_lock(this->cacheLock);
for(auto& entry : this->cachedClients) {
lock_guard entry_lock(entry->lock);
entry->server_groups.erase(std::remove_if(entry->server_groups.begin(), entry->server_groups.end(), [&](const std::shared_ptr<GroupAssignment>& group_assignment) {
return group_assignment->group == group;
}), entry->server_groups.end());
for(auto it = entry->channel_groups.begin(); it != entry->channel_groups.end();) {
if(it->second->group == group)
it = entry->channel_groups.erase(it);
else
it++;
}
}
}
bool flag_sql = false;
auto res = sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}).execute();
LOG_SQL_CMD(res);
flag_sql |= !res;
res = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}).execute();
LOG_SQL_CMD(res);
flag_sql |= !res;
flag_sql &= serverInstance->databaseHelper()->deleteGroupPermissions(this->server.lock(), group->groupId());
if(flag_sql)
logError(this->getServerId(), "Could not delete group {} ({}) from database. May leader to invalid data", group->name(), group->groupId());
return true;
}
int64_t GroupManager::generateGroupId(sql::SqlManager* sql) {
int64_t hightestGroupId = 0;
sql::command(sql, "SELECT `groupId` FROM `groups` ORDER BY `groupId` DESC LIMIT 1").query([](int64_t* ptr, int, char** values, char**){
*ptr = stoul(values[0]);
return 0;
}, &hightestGroupId);
return hightestGroupId + 1;
}
std::deque<property::ClientProperties> GroupManager::update_server_group_property(const shared_ptr<server::ConnectedClient> &client, bool channel_lock, const std::shared_ptr<BasicChannel>& channel) {
std::deque<property::ClientProperties> changed;
//Server groups
{
auto groups = this->getServerGroups(client->getClientDatabaseId(), client->getType());
string group_string;
for(const auto& group : groups)
if(group_string.empty()) group_string += to_string(group->group->groupId());
else group_string += "," + to_string(group->group->groupId());
if(client->properties()[property::CLIENT_SERVERGROUPS] != group_string) {
client->properties()[property::CLIENT_SERVERGROUPS] = group_string;
changed.push_back(property::CLIENT_SERVERGROUPS);
unique_lock chan_lock(client->channel_lock, defer_lock);
if(channel_lock)
chan_lock.lock();
}
}
//Channel groups
if(channel){
shared_ptr<GroupAssignment> group = this->getChannelGroup(client->getClientDatabaseId(), channel, true);
if(client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] != group->channelId) {
client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] = group->channelId;
changed.push_back(property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID);
}
if(client->properties()[property::CLIENT_CHANNEL_GROUP_ID] != group->group->groupId()) {
client->properties()[property::CLIENT_CHANNEL_GROUP_ID] = group->group->groupId();
changed.push_back(property::CLIENT_CHANNEL_GROUP_ID);
unique_lock chan_lock(client->channel_lock, defer_lock);
if(channel_lock)
chan_lock.lock();
}
}
if(!changed.empty())
client->join_state_id++; /* groups have changed :) */
return changed;
}
void GroupManager::cleanupAssignments(ClientDbId client) {
if(this->root)
this->root->cleanupAssignments(client);
for(const auto& assignment : this->getAssignedServerGroups(client)) {
if(!assignment->group->is_permanent())
this->removeServerGroup(client, assignment->group);
}
}
void GroupManager::enableCache(const ClientDbId& client_database_id) {
if(this->root)
this->root->enableCache(client_database_id);
unique_lock cache_lock(this->cacheLock);
/* test if we're already having the client */
for(auto& entry : this->cachedClients)
if(entry->client_database_id == client_database_id) {
entry->use_count++;
return; /* client already cached, no need to cache client */
}
auto entry = std::make_shared<CachedClient>();
entry->client_database_id = client_database_id;
entry->use_count++;
this->cachedClients.push_back(entry);
lock_guard client_cache_lock(entry->lock); /* lock the client because we're currently loading the cache. */
cache_lock.unlock();
auto res = sql::command(this->sql, "SELECT `groupId`, `channelId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->getServerId()}, variable{":cldbid", client_database_id})
.query([&](int length, std::string* value, std::string* column) {
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
ChannelId channelId = 0;
for(int index = 0; index < length; index++){
try {
if(column[index] == "groupId"){
group = this->findGroup(stoll(value[index]));
} else if(column[index] == "until"){
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
} else if(column[index] == "channelId"){
channelId = stoll(value[index]);
} else {
logError(this->getServerId(), "Unknown column in group assignment query: {}", column[index]);
continue;
}
} catch(std::exception& ex) {
logError(this->getServerId(), "Failed to load group assignment from database for client {}. Column {} contains an invalid value: {}", client_database_id, column[index], value[index]);
return 0;
}
}
if(!group) {
return 0;
}
auto assignment = std::make_shared<GroupAssignment>();
assignment->group = group;
assignment->until = until;
assignment->parent = &*entry;
assignment->channelId = channelId;
assignment->server = this->getServerId();
if(channelId == 0)
entry->server_groups.push_back(assignment);
else
entry->channel_groups[channelId] = assignment;
return 0;
});
}
//FIXME: This method till get far more often then it should be. We should add a flag if the group cache is loaded for each std::shared_ptr<ConnectedClient> instance
void GroupManager::disableCache(const ClientDbId& client_database_id) {
if(this->root) {
this->root->disableCache(client_database_id);
}
lock_guard cache_lock(this->cacheLock);
this->cachedClients.erase(std::remove_if(this->cachedClients.begin(), this->cachedClients.end(), [&](const std::shared_ptr<CachedClient>& client) {
if(client->client_database_id != client_database_id)
return false;
lock_guard client_lock{client->lock};
return (--client->use_count) == 0;
}), this->cachedClients.end());
}
void GroupManager::clearCache() {
if(this->root) this->root->clearCache();
lock_guard lock(this->cacheLock);
this->cachedClients.clear();
}
bool GroupManager::isClientCached(const ClientDbId& client_database_id) {
return this->resolve_cached_client(client_database_id) == nullptr;
}
typedef std::vector<std::shared_ptr<GroupMember>> ResList;
std::vector<std::shared_ptr<GroupMember>> GroupManager::listGroupMembers(std::shared_ptr<Group> group, bool names) { //TODO juse inner join only on names = true
if(!isLocalGroup(group)){
if(this->root) return this->root->listGroupMembers(group, names);
return {};
}
ResList result;
sql::command(this->sql,
"SELECT assignedGroups.cldbid, assignedGroups.channelId, assignedGroups.until, clients.clientUid, clients.lastName FROM assignedGroups INNER JOIN clients ON (clients.cldbid = assignedGroups.cldbid AND clients.serverId = assignedGroups.serverId) WHERE assignedGroups.`serverId` = :sid AND `groupId` = :gid;",
variable{":sid", this->getServerId()}, variable{":gid", group->groupId()})
.query([&](ResList* list, int columnCount, char** values, char** columnName){
std::shared_ptr<GroupMember> member = std::make_shared<GroupMember>();
member->displayName = "undefined";
member->uid = "undefined";
for(int index = 0; index < columnCount; index++){
if(values[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + columnName[index]);
continue;
}
if(strcmp(columnName[index], "cldbid") == 0)
member->cldbId = stoll(values[index]);
else if(strcmp(columnName[index], "until") == 0)
member->until = time_point<system_clock>() + milliseconds(stoll(values[index]));
else if(strcmp(columnName[index], "clientUid") == 0)
member->uid = values[index];
else if(strcmp(columnName[index], "lastName") == 0)
member->displayName = values[index];
else if(strcmp(columnName[index], "channelId") == 0)
member->channelId = stoll(values[index]);
else cerr << "Invalid column name " << columnName[index] << endl;
}
list->push_back(member);
return 0;
}, &result);
return result;
}
vector<shared_ptr<GroupAssignment>> GroupManager::listGroupAssignments(ClientDbId cldbId) {
vector<std::shared_ptr<GroupAssignment>> result;
sql::result res;
auto cached = resolve_cached_client(cldbId);
if(cached) {
{
lock_guard lock{cached->lock};
for(const auto &serverGroup : cached->server_groups)
result.push_back(serverGroup);
for(auto& channelGroup : cached->channel_groups)
result.push_back(channelGroup.second);
}
if(this->root){
auto append = this->root->listGroupAssignments(cldbId);
for(const auto &elm : append)
result.push_back(elm);
}
return result;
}
res = sql::command(this->sql, "SELECT `groupId`, `until`, `channelId` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->getServerId()}, variable{":cldbid", cldbId})
.query([&](int length, char** value, char** column){
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
uint64_t channelId = 0;
for(int index = 0; index < length; index++){
if(value[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
continue;
}
if(strcmp(column[index], "groupId") == 0){
group = this->findGroup(stoll(value[index]));
} else if(strcmp(column[index], "until") == 0){
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
} else if(strcmp(column[index], "channelId") == 0){
channelId = stoll(value[index]);
} else cerr << "Invalid column " << column[index] << endl;
}
if(!group)
return 0;
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
assignment->parent = nullptr;
assignment->group = group;
assignment->until = until;
assignment->channelId = channelId;
assignment->server = this->getServerId();
result.push_back(assignment);
return 0;
});
(LOG_SQL_CMD)(res);
if(this->root){
auto append = this->root->listGroupAssignments(cldbId);
for(const auto &elm : append)
result.push_back(elm);
}
return result;
}
std::shared_ptr<CachedClient> GroupManager::resolve_cached_client(ClientDbId client_database_id) {
{
lock_guard lock(this->cacheLock);
for(auto& entry : this->cachedClients)
if(entry->client_database_id == client_database_id)
return entry;
}
return nullptr;
}
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::getAssignedServerGroups(ClientDbId cldbid) {
auto cached = this->resolve_cached_client(cldbid);
sql::result res;
std::vector<std::shared_ptr<GroupAssignment>> result;
if(this->root) {
auto client_groups = this->root->getAssignedServerGroups(cldbid);
result.insert(result.begin(), client_groups.begin(), client_groups.end());
}
if(cached) {
lock_guard cache_lock{cached->lock};
result.insert(result.end(), cached->server_groups.begin(), cached->server_groups.end());
return result;
}
debugMessage(this->getServerId(), "Query client groups for client {} on server {}.", cldbid, this->getServerId());
res = sql::command(this->sql, "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = 0", variable{":sid", this->getServerId()}, variable{":cldbid", cldbid}).query([&](int length, char** value, char** column){
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
for(int index = 0; index < length; index++) {
if(value[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
continue;
}
if(strcmp(column[index], "groupId") == 0 && value[index] != nullptr){
group = this->findGroup(stoll(value[index]));
} else if(strcmp(column[index], "until") == 0){
until = time_point<system_clock>() + milliseconds(stoll(value[index] == nullptr ? "0" : value[index]));
} else cerr << "Invalid column " << column[index] << endl;
}
if(!group)
return 0;
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
assignment->parent = nullptr;
assignment->group = group;
assignment->until = until;
assignment->server = this->getServerId();
result.push_back(assignment);
return 0;
});
LOG_SQL_CMD(res);
return result;
}
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::getServerGroups(ClientDbId cldbid, server::ClientType type) {
auto result = this->getAssignedServerGroups(cldbid);
if(result.empty()) return this->defaultServerGroupGroupAssignments(cldbid, type);
return result;
}
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::defaultServerGroupGroupAssignments(ClientDbId client, ClientType type) {
std::vector<std::shared_ptr<GroupAssignment>> result;
if(type == ClientType::CLIENT_QUERY && this->root) {
auto root = this->root->defaultServerGroupGroupAssignments(client, type);
result.insert(result.begin(), root.begin(), root.end());
} else if(type == ClientType::CLIENT_MUSIC) {
threads::MutexLock lock(this->cacheLock);
auto server = this->server.lock();
auto id =
server ?
server->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP].as_save<GroupId>() :
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_save<GroupId>();
auto group = this->findGroupLocal(id);
if(group) {
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, group, time_point<system_clock>()));
return result;
}
}
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, this->defaultGroup(GroupTarget::GROUPTARGET_SERVER), time_point<system_clock>()));
return result;
}
std::shared_ptr<GroupAssignment> GroupManager::getChannelGroupExact(ClientDbId cldbId, const std::shared_ptr<BasicChannel>& channel, bool assign_default) {
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
if(cached->channel_groups.count(channel->channelId()) > 0) {
return cached->channel_groups[channel->channelId()];
} else
return assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : nullptr;
}
std::shared_ptr<GroupAssignment> result;
auto res = sql::command(this->sql, "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid", variable{":sid", this->getServerId()}, variable{":cldbid", cldbId}, variable{":chid", channel->channelId()}).query([&](int length, char** value, char** column){
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
for(int index = 0; index < length; index++){
if(value[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
continue;
}
if(strcmp(column[index], "groupId") == 0){
group = this->findGroup(stoll(value[index]));
} else if(strcmp(column[index], "until") == 0){
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
} else cerr << "Invalid column " << column[index] << endl;
}
if(!group)
return 0;
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
assignment->parent = nullptr;
assignment->group = group;
assignment->until = until;
assignment->server = this->getServerId();
assignment->channelId = channel->channelId();
result = std::move(assignment);
return 0;
});
(LOG_SQL_CMD)(res);
return !result && assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : result;
}
std::shared_ptr<GroupAssignment> GroupManager::getChannelGroup(ClientDbId cldbId, const shared_ptr<BasicChannel> &channel, bool assign_default) {
shared_ptr<GroupAssignment> group;
std::shared_ptr<BasicChannel> inheritance_channel = channel;
while(inheritance_channel && !group) {
group = this->getChannelGroupExact(cldbId, inheritance_channel, false);
if(!group) {
auto inheritance = inheritance_channel->permissions()->permission_value_flagged(permission::b_channel_group_inheritance_end);
if(inheritance.has_value && inheritance.value == 1)
break;
inheritance_channel = inheritance_channel->parent();
}
}
return !group && assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : group;
}
std::shared_ptr<GroupAssignment> GroupManager::defaultChannelGroupAssignment(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &channel) {
return std::make_shared<GroupAssignment>(nullptr, this->getServerId(), channel->channelId(), this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL), time_point<system_clock>());
}
void GroupManager::addServerGroup(ClientDbId cldbId, std::shared_ptr<Group> group, time_point<system_clock> until) {
/*
if(!this->isLocalGroup(group)) {
if(this->root) this->root->addServerGroup(cldbId, group, until);
return;
}
if(hasServerGroup(cldbId, group)) return;
*/
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
cached->server_groups.push_back(std::make_shared<GroupAssignment>(cached.get(), this->getServerId(), 0, group, until));
}
sql::command(this->sql, "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":gid", group->groupId()},
variable{":chid", 0},
variable{":until", time_point_cast<milliseconds>(until).time_since_epoch().count()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
void GroupManager::removeServerGroup(ClientDbId cldbId, std::shared_ptr<Group> group) {
if(!this->hasServerGroupAssigned(cldbId, group)) return;
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
cached->server_groups.erase(std::remove_if(cached->server_groups.begin(), cached->server_groups.end(), [&](const std::shared_ptr<GroupAssignment>& group_assignment) {
return group_assignment->group == group;
}), cached->server_groups.end());
}
sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `groupId` = :gid AND `channelId` = :chid",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":gid", group->groupId()},
variable{":chid", 0})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
void GroupManager::setChannelGroup(ClientDbId cldbId, std::shared_ptr<Group> group, std::shared_ptr<BasicChannel> channel, time_point<system_clock> until) {
auto old_group = getChannelGroupExact(cldbId, channel, false);
if(old_group) {
if(old_group->group == group) return;
} else if(!group) return;
auto default_group = !group || group == this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
if(default_group)
cached->channel_groups.erase(channel->channelId());
else
cached->channel_groups[channel->channelId()] = std::make_shared<GroupAssignment>(cached.get(), this->getServerId(), channel->channelId(), group, until);
}
sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":chid", channel->channelId()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
if(!default_group) {
sql::command(this->sql, "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":gid", group->groupId()},
variable{":chid", channel->channelId()},
variable{":until", time_point_cast<milliseconds>(until).time_since_epoch().count()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
}