558 lines
27 KiB
C++
558 lines
27 KiB
C++
#include <cstring>
|
|
#include <protocol/buffers.h>
|
|
#include "client/voice/VoiceClient.h"
|
|
#include "client/InternalClient.h"
|
|
#include "VirtualServer.h"
|
|
#include <misc/timer.h>
|
|
#include <log/LogUtils.h>
|
|
#include <misc/sassert.h>
|
|
#include "InstanceHandler.h"
|
|
|
|
using namespace std;
|
|
using namespace ts::server;
|
|
using namespace ts::protocol;
|
|
using namespace ts::buffer;
|
|
using namespace ts::permission;
|
|
using namespace std::chrono;
|
|
|
|
bool VirtualServer::registerClient(shared_ptr<ConnectedClient> client) {
|
|
sassert(client);
|
|
|
|
{
|
|
lock_guard lock(this->clients.lock);
|
|
if(client->getClientId() > 0) {
|
|
logCritical(this->getServerId(), "Client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid());
|
|
return false;
|
|
}
|
|
|
|
ClientId client_id = 0;
|
|
ClientId max_client_id = this->clients.clients.size();
|
|
|
|
while(client_id < max_client_id && this->clients.clients[client_id])
|
|
client_id++;
|
|
if(client_id == max_client_id)
|
|
this->clients.clients.push_back(client);
|
|
else
|
|
this->clients.clients[client_id] = client;
|
|
this->clients.count++;
|
|
client->setClientId(client_id);
|
|
}
|
|
|
|
{
|
|
lock_guard lock(this->client_nickname_lock);
|
|
|
|
auto login_name = client->getDisplayName();
|
|
while(login_name.length() < 3)
|
|
login_name += ".";
|
|
|
|
if(client->getExternalType() == ClientType::CLIENT_TEAMSPEAK)
|
|
client->properties()[property::CLIENT_LOGIN_NAME] = login_name;
|
|
|
|
std::shared_ptr<ConnectedClient> found_client = nullptr;
|
|
|
|
auto client_name = login_name;
|
|
size_t counter = 0;
|
|
|
|
{
|
|
lock_guard clients_lock(this->clients.lock);
|
|
while(true) {
|
|
for(auto& _client : this->clients.clients) {
|
|
if(!_client) continue;
|
|
|
|
if(_client->getDisplayName() == client_name && _client != client)
|
|
goto increase_name;
|
|
}
|
|
goto nickname_valid;
|
|
|
|
increase_name:
|
|
client_name = login_name + to_string(++counter);
|
|
}
|
|
}
|
|
nickname_valid:
|
|
client->setDisplayName(client_name);
|
|
}
|
|
|
|
|
|
|
|
if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_WEB) {
|
|
this->properties()[property::VIRTUALSERVER_CLIENT_CONNECTIONS] ++; //increase manager connections
|
|
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
|
}
|
|
else if(client->getType() == ClientType::CLIENT_QUERY) {
|
|
this->properties()[property::VIRTUALSERVER_LAST_QUERY_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
|
this->properties()[property::VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS] ++; //increase manager connections
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> cl, std::string reason, std::unique_lock<std::shared_mutex>& chan_tree_lock) {
|
|
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK && cl->getType() == ClientType::CLIENT_WEB) {
|
|
sassert(cl->state == ConnectionState::DISCONNECTED);
|
|
}
|
|
|
|
auto client_id = cl->getClientId();
|
|
if(client_id == 0) return false; /* not registered */
|
|
{
|
|
|
|
lock_guard lock(this->clients.lock);
|
|
if(client_id >= this->clients.clients.size()) {
|
|
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister client.", cl->getDisplayName(), client_id, cl->getUid());
|
|
} else {
|
|
auto& client_container = this->clients.clients[client_id];
|
|
if(client_container != cl) {
|
|
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister client.", cl->getDisplayName(), client_id, cl->getUid());
|
|
} else {
|
|
client_container.reset();
|
|
this->clients.count--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK || cl->getType() == ClientType::CLIENT_WEB)
|
|
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
|
else if(cl->getType() == ClientType::CLIENT_QUERY)
|
|
this->properties()[property::VIRTUALSERVER_LAST_QUERY_DISCONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
|
|
|
{
|
|
if(!chan_tree_lock.owns_lock())
|
|
chan_tree_lock.lock();
|
|
|
|
if(cl->currentChannel) //We dont have to make him invisible if he hasnt even a channel
|
|
this->client_move(cl, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock);
|
|
}
|
|
|
|
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
|
|
cl->setClientId(0);
|
|
return true;
|
|
}
|
|
|
|
void VirtualServer::registerInternalClient(std::shared_ptr<ConnectedClient> client) {
|
|
client->state = ConnectionState::CONNECTED;
|
|
{
|
|
lock_guard lock(this->clients.lock);
|
|
if(client->getClientId() > 0) {
|
|
logCritical(this->getServerId(), "Internal client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid());
|
|
return;
|
|
}
|
|
|
|
ClientId client_id = 0;
|
|
ClientId max_client_id = this->clients.clients.size();
|
|
while(client_id < max_client_id && this->clients.clients[client_id])
|
|
client_id++;
|
|
if(client_id == max_client_id)
|
|
this->clients.clients.push_back(client);
|
|
else
|
|
this->clients.clients[client_id] = client;
|
|
|
|
this->clients.clients[client_id] = client;
|
|
this->clients.count++;
|
|
client->setClientId(client_id);
|
|
}
|
|
}
|
|
|
|
void VirtualServer::unregisterInternalClient(std::shared_ptr<ConnectedClient> client) {
|
|
client->state = ConnectionState::DISCONNECTED;
|
|
|
|
{
|
|
auto client_id = client->getClientId();
|
|
|
|
lock_guard lock(this->clients.lock);
|
|
if(client_id >= this->clients.clients.size()) {
|
|
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister internal client.", client->getDisplayName(), client_id, client->getUid());
|
|
} else {
|
|
auto& client_container = this->clients.clients[client_id];
|
|
if(client_container != client) {
|
|
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister internal client.", client->getDisplayName(), client_id, client->getUid());
|
|
} else {
|
|
this->clients.count--;
|
|
client_container.reset();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool VirtualServer::assignDefaultChannel(const shared_ptr<ConnectedClient>& client, bool join) {
|
|
shared_lock server_channel_lock(this->channel_tree_lock);
|
|
std::shared_ptr<BasicChannel> channel = nullptr;
|
|
if(client->properties()->hasProperty(property::CLIENT_DEFAULT_CHANNEL) && !client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<string>().empty()) {
|
|
auto str = client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>();
|
|
if(str[0] == '/' && str.find_first_not_of("0123456789", 1) == string::npos)
|
|
channel = this->channelTree->findChannel(static_cast<ChannelId>(stoll(str.substr(1))));
|
|
else
|
|
channel = this->channelTree->findChannelByPath(str);
|
|
if (channel) {
|
|
if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) {
|
|
logMessage(this->serverId, "{} Client tried to connect to a channel which he hasn't permission for. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name());
|
|
channel = nullptr;
|
|
} else if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true) && !permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
|
|
logMessage(this->serverId, "{} Client tried to connect to a channel which is password protected and he hasn't the right password. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name());
|
|
channel = nullptr;
|
|
}
|
|
} else
|
|
logMessage(this->serverId, "{} Client {}/{} tried to join on a not existing channel. Name: {}",
|
|
CLIENT_STR_LOG_PREFIX_(client),
|
|
client->getDisplayName(), client->getUid(),
|
|
client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>());
|
|
}
|
|
if(!channel) channel = this->channelTree->getDefaultChannel();
|
|
if(!channel) return false;
|
|
|
|
if(join) {
|
|
server_channel_lock.unlock();
|
|
unique_lock server_channel_w_lock(this->channel_tree_lock);
|
|
this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_w_lock);
|
|
} else {
|
|
client->currentChannel = channel;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void VirtualServer::testBanStateChange(const std::shared_ptr<ConnectedClient>& invoker) {
|
|
this->forEachClient([&](shared_ptr<ConnectedClient> client) {
|
|
auto ban = client->resolveActiveBan(client->getPeerIp());
|
|
if(ban) {
|
|
logMessage(this->getServerId(), "Client {} was online, but had an ban whcih effect him has been registered. Disconnecting client.", CLIENT_STR_LOG_PREFIX_(client));
|
|
auto entryTime = ban->until.time_since_epoch().count() > 0 ? (uint64_t) chrono::ceil<seconds>(ban->until - system_clock::now()).count() : 0UL;
|
|
this->notify_client_ban(client, invoker, ban->reason, entryTime);
|
|
client->close_connection(system_clock::now() + seconds(1));
|
|
}
|
|
});
|
|
}
|
|
|
|
bool VirtualServer::could_default_create_channel() {
|
|
{
|
|
|
|
auto default_group = this->getGroupManager()->defaultGroup(GroupTarget::GROUPTARGET_SERVER);
|
|
if(default_group) {
|
|
auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1;
|
|
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1;
|
|
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1;
|
|
if(flag)
|
|
return true;
|
|
}
|
|
}
|
|
{
|
|
|
|
auto default_group = this->getGroupManager()->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
|
|
if(default_group) {
|
|
auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1;
|
|
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1;
|
|
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1;
|
|
if(flag)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
*
|
|
for (auto &cl : this->server->getClients())
|
|
if (cl->isClientVisible(client) || client == cl)
|
|
cl->notifyClientLeftViewKicked(client, client->currentChannel, nullptr, cmd["reasonmsg"].as<std::string>(), this);
|
|
*/
|
|
|
|
void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target, const std::shared_ptr<ts::server::ConnectedClient> &invoker, const std::string &reason, size_t time) {
|
|
/* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */
|
|
lock_guard command_lock(target->command_lock);
|
|
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
|
|
|
|
if(target->currentChannel) {
|
|
for(const auto& client : this->getClients()) {
|
|
if(!client || client == target)
|
|
continue;
|
|
|
|
unique_lock client_channel_lock(client->channel_lock);
|
|
if(client->isClientVisible(target, false))
|
|
client->notifyClientLeftViewBanned(target, reason, invoker, time, false);
|
|
}
|
|
|
|
auto s_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
|
|
s_channel->unregister_client(target);
|
|
}
|
|
|
|
/* now disconnect the target itself */
|
|
unique_lock client_channel_lock(target->channel_lock);
|
|
target->notifyClientLeftViewBanned(target, reason, invoker, time, false);
|
|
target->currentChannel = nullptr;
|
|
}
|
|
|
|
void VirtualServer::notify_client_kick(
|
|
const std::shared_ptr<ts::server::ConnectedClient> &target,
|
|
const std::shared_ptr<ts::server::ConnectedClient> &invoker,
|
|
const std::string &reason,
|
|
const std::shared_ptr<ts::BasicChannel> &target_channel) {
|
|
|
|
if(target_channel) {
|
|
/* use the move! */
|
|
unique_lock server_channel_lock(this->channel_tree_lock, defer_lock);
|
|
this->client_move(target, target_channel, invoker, reason, ViewReasonId::VREASON_CHANNEL_KICK, true, server_channel_lock);
|
|
} else {
|
|
/* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */
|
|
lock_guard command_lock(target->command_lock);
|
|
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
|
|
|
|
if(target->currentChannel) {
|
|
for(const auto& client : this->getClients()) {
|
|
if(!client || client == target)
|
|
continue;
|
|
|
|
unique_lock client_channel_lock(client->channel_lock);
|
|
if(client->isClientVisible(target, false))
|
|
client->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
|
|
}
|
|
|
|
auto s_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
|
|
s_channel->unregister_client(target);
|
|
}
|
|
|
|
/* now disconnect the target itself */
|
|
unique_lock client_channel_lock(target->channel_lock);
|
|
target->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
|
|
target->currentChannel = nullptr;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 1. flag channel as deleted (lock channel tree so no moves)
|
|
* 2. Gather all clients within the channel (lock their execute lock)
|
|
* 3. Unlock channel tree and lock client locks
|
|
* 4. lock channel tree again and move the clients (No new clients should be joined because channel is flagged as deleted!)
|
|
*
|
|
* Note: channel cant be a ref because the channel itself gets deleted!
|
|
*/
|
|
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock) {
|
|
if(!tree_lock.owns_lock())
|
|
tree_lock.lock();
|
|
if(channel->deleted)
|
|
return;
|
|
|
|
deque<std::shared_ptr<ConnectedClient>> clients;
|
|
{
|
|
for(const auto& sub_channel : this->channelTree->channels(channel)) {
|
|
auto s_channel = dynamic_pointer_cast<ServerChannel>(sub_channel);
|
|
assert(s_channel);
|
|
|
|
auto chan_clients = this->getClientsByChannel(sub_channel);
|
|
clients.insert(clients.end(), chan_clients.begin(), chan_clients.end());
|
|
s_channel->deleted = true;
|
|
}
|
|
auto chan_clients = this->getClientsByChannel(channel);
|
|
clients.insert(clients.end(), chan_clients.begin(), chan_clients.end());
|
|
channel->deleted = true;
|
|
}
|
|
auto default_channel = this->channelTree->getDefaultChannel();
|
|
tree_lock.unlock();
|
|
|
|
deque<unique_lock<threads::Mutex>> command_locks;
|
|
for(const auto& client : clients) {
|
|
command_locks.push_back(move(unique_lock(client->command_lock)));
|
|
}
|
|
|
|
for(const auto& client : clients)
|
|
this->client_move(client, default_channel, invoker, kick_message, ViewReasonId::VREASON_CHANNEL_KICK, true, tree_lock);
|
|
|
|
if(!tree_lock.owns_lock())
|
|
tree_lock.lock(); /* no clients left within that tree */
|
|
command_locks.clear();
|
|
|
|
auto channel_ids = this->channelTree->delete_channel_root(channel);
|
|
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
|
|
unique_lock client_channel_lock(client->channel_lock);
|
|
client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker);
|
|
});
|
|
}
|
|
|
|
void VirtualServer::client_move(
|
|
const shared_ptr<ts::server::ConnectedClient> &target,
|
|
shared_ptr<ts::BasicChannel> target_channel,
|
|
const std::shared_ptr<ts::server::ConnectedClient> &invoker,
|
|
const std::string &reason_message,
|
|
ts::ViewReasonId reason_id,
|
|
bool notify_client,
|
|
std::unique_lock<std::shared_mutex> &server_channel_write_lock) {
|
|
|
|
TIMING_START(timings);
|
|
if(server_channel_write_lock.owns_lock())
|
|
server_channel_write_lock.unlock();
|
|
|
|
lock_guard client_command_lock(target->command_lock);
|
|
server_channel_write_lock.lock();
|
|
TIMING_STEP(timings, "chan tree l");
|
|
if(target->currentChannel == target_channel)
|
|
return;
|
|
|
|
/* first step: resolve the target channel / or fix missing */
|
|
auto s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
|
|
auto s_source_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
|
|
assert(!target->currentChannel || s_source_channel != nullptr);
|
|
|
|
deque<property::ClientProperties> client_updates;
|
|
std::deque<property::ClientProperties> changed_groups{};
|
|
if(target_channel) {
|
|
assert(s_target_channel);
|
|
if(s_target_channel->deleted) {
|
|
target_channel = this->channelTree->getDefaultChannel();
|
|
s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
|
|
assert(s_target_channel);
|
|
}
|
|
|
|
/* update the group properties here already, so for all enter views we could just send the new props directly */
|
|
changed_groups = this->groups->update_server_group_property(target, true, s_target_channel);
|
|
client_updates.insert(client_updates.end(), changed_groups.begin(), changed_groups.end()); //TODO: Only update for clients which have no enter?
|
|
}
|
|
auto l_target_channel = s_target_channel ? this->channelTree->findLinkedChannel(s_target_channel->channelId()) : nullptr;
|
|
auto l_source_channel = s_source_channel ? this->channelTree->findLinkedChannel(s_source_channel->channelId()) : nullptr;
|
|
TIMING_STEP(timings, "channel res");
|
|
|
|
/* second step: show the target channel to the client if its not shown and let him subscibe to the channel */
|
|
if(target_channel && notify_client) {
|
|
unique_lock client_channel_lock(target->channel_lock);
|
|
|
|
bool success = false;
|
|
/* TODO: Use a bunk here and not a notify for every single */
|
|
for(const auto& channel : target->channels->show_channel(l_target_channel, success))
|
|
target->notifyChannelShow(channel->channel(), channel->previous_channel);
|
|
sassert(success);
|
|
if(!success)
|
|
return;
|
|
|
|
target->subscribeChannel({target_channel}, false, true);
|
|
}
|
|
TIMING_STEP(timings, "target show");
|
|
|
|
if(target_channel) {
|
|
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
|
|
if (!notify_client && client == target) return;
|
|
|
|
unique_lock client_channel_lock(client->channel_lock);
|
|
auto chan_target = client->channels->find_channel(target_channel);
|
|
|
|
if(chan_target) {
|
|
auto chan_source = client->channels->find_channel(s_source_channel);
|
|
if(chan_source) {
|
|
if (chan_target->subscribed || client == target) {
|
|
if (client == target || client->isClientVisible(target, false)) {
|
|
client->notifyClientMoved(target, s_target_channel, reason_id, reason_message, invoker, false);
|
|
} else {
|
|
client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, reason_id, s_source_channel, false);
|
|
}
|
|
} else if(client->isClientVisible(target, false)){
|
|
//Client got out of view
|
|
client->notifyClientLeftView(target, s_target_channel, reason_id, reason_message.empty() ? string("view left") : reason_message, invoker, false);
|
|
}
|
|
} else {
|
|
if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC)
|
|
logCritical(this->getServerId(), "{} Client enters visibility twice!", CLIENT_STR_LOG_PREFIX_(client));
|
|
|
|
//Client entered view
|
|
if(chan_target->subscribed)
|
|
client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, ViewReasonId::VREASON_USER_ACTION, nullptr, false);
|
|
}
|
|
} else {
|
|
/* target channel isn't visible => so client gone out of view */
|
|
if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC)
|
|
logCritical(this->getServerId(), "{} Moving own client into a not visible channel! This shall not happen!", CLIENT_STR_LOG_PREFIX_(client));
|
|
//Test for in view? (Notify already does but nvm)
|
|
|
|
if(client->isClientVisible(target, false)){
|
|
//Client got out of view
|
|
if(reason_id == ViewReasonId::VREASON_USER_ACTION)
|
|
client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "joined a hidden channel" : reason_message, invoker, false);
|
|
else
|
|
client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "moved to a hidden channel" : reason_message, invoker, false);
|
|
}
|
|
}
|
|
});
|
|
|
|
if(s_source_channel)
|
|
s_source_channel->unregister_client(target);
|
|
s_target_channel->register_client(target);
|
|
} else {
|
|
/* client left the server */
|
|
if(target->currentChannel) {
|
|
for(const auto& client : this->getClients()) {
|
|
if(!client || client == target)
|
|
continue;
|
|
|
|
unique_lock client_channel_lock(client->channel_lock);
|
|
if(client->isClientVisible(target, false))
|
|
client->notifyClientLeftView(target, nullptr, reason_id, reason_message, invoker, false);
|
|
}
|
|
|
|
s_source_channel->unregister_client(target);
|
|
}
|
|
}
|
|
TIMING_STEP(timings, "notify view");
|
|
|
|
target->currentChannel = target_channel;
|
|
server_channel_write_lock.unlock();
|
|
|
|
/* third step: update stuff for the client (remember: the client cant execute anything at the moment!) */
|
|
shared_lock server_channel_read_lock(this->channel_tree_lock);
|
|
unique_lock client_channel_lock(target->channel_lock);
|
|
TIMING_STEP(timings, "lock own tr");
|
|
|
|
if (s_source_channel) {
|
|
s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
|
|
auto source_channel_group = this->groups->getChannelGroupExact(target->getClientDatabaseId(), s_source_channel, false);
|
|
if(source_channel_group) {
|
|
auto default_data = source_channel_group->group->permissions()->permission_value_flagged(permission::b_group_is_permanent);
|
|
if(!default_data.has_value || default_data.value != 1)
|
|
this->groups->setChannelGroup(target->getClientDatabaseId(), nullptr, s_source_channel);
|
|
}
|
|
|
|
auto update = target->properties()[property::CLIENT_IS_TALKER].as<bool>() || target->properties()[property::CLIENT_TALK_REQUEST].as<int64_t>() > 0;
|
|
if(update) {
|
|
target->properties()[property::CLIENT_IS_TALKER] = 0;
|
|
target->properties()[property::CLIENT_TALK_REQUEST] = 0;
|
|
target->properties()[property::CLIENT_TALK_REQUEST_MSG] = "";
|
|
client_updates.push_back(property::CLIENT_IS_TALKER);
|
|
client_updates.push_back(property::CLIENT_TALK_REQUEST);
|
|
client_updates.push_back(property::CLIENT_TALK_REQUEST_MSG);
|
|
}
|
|
TIMING_STEP(timings, "src chan up");
|
|
}
|
|
|
|
if (s_target_channel) {
|
|
if(target->update_cached_permissions()) /* update cached calculated permissions */
|
|
target->sendNeededPermissions(false);
|
|
TIMING_STEP(timings, "perm gr upd");
|
|
|
|
if(s_source_channel) {
|
|
deque<ChannelId> deleted;
|
|
for(const auto& channel : target->channels->test_channel(l_source_channel, l_target_channel))
|
|
deleted.push_back(channel->channelId());
|
|
|
|
if(!deleted.empty())
|
|
target->notifyChannelHide(deleted, false);
|
|
|
|
auto i_source_channel = s_source_channel->channelId();
|
|
if(std::find(deleted.begin(), deleted.end(), i_source_channel) == deleted.end()) {
|
|
auto source_channel_sub_power = target->calculate_permission(permission::i_channel_subscribe_power, i_source_channel);
|
|
if(!s_source_channel->permission_granted(permission::i_channel_needed_subscribe_power, source_channel_sub_power, false)) {
|
|
auto source_channel_sub_power_ignore = target->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel);
|
|
if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore, true)) {
|
|
logTrace(this->serverId, "Force unsubscribing of client {} for channel {}/{}. (Channel switch and no permissions)",
|
|
CLIENT_STR_LOG_PREFIX_(target), s_source_channel->name(),
|
|
i_source_channel
|
|
);
|
|
target->unsubscribeChannel({s_source_channel}, false); //Unsubscribe last channel (hasn't permissions)
|
|
}
|
|
}
|
|
}
|
|
TIMING_STEP(timings, "src hide ts");
|
|
}
|
|
}
|
|
client_channel_lock.unlock();
|
|
/* both methods lock if they require stuff */
|
|
this->notifyClientPropertyUpdates(target, client_updates, s_source_channel ? true : false);
|
|
TIMING_STEP(timings, "notify cpro");
|
|
if(s_target_channel) {
|
|
target->updateChannelClientProperties(false, s_source_channel ? true : false);
|
|
TIMING_STEP(timings, "notify_t_pr");
|
|
}
|
|
debugMessage(this->getServerId(), "{} Client move timings: {}", CLIENT_STR_LOG_PREFIX_(target), TIMING_FINISH(timings));
|
|
} |