Teaspeak-Server/server/src/TS3ServerClientManager.cpp

564 lines
27 KiB
C++
Raw Normal View History

#include <cstring>
#include <protocol/buffers.h>
#include "client/voice/VoiceClient.h"
#include "client/InternalClient.h"
2020-01-26 12:04:38 -05:00
#include "VirtualServer.h"
#include <misc/timer.h>
#include <log/LogUtils.h>
#include <misc/sassert.h>
2020-06-28 08:01:14 -04:00
#include <src/manager/ActionLogger.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;
2020-01-26 12:04:38 -05:00
bool VirtualServer::registerClient(shared_ptr<ConnectedClient> client) {
2020-01-23 20:57:58 -05:00
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;
}
2020-01-26 12:04:38 -05:00
bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> cl, std::string reason, std::unique_lock<std::shared_mutex>& chan_tree_lock) {
2020-01-23 20:57:58 -05:00
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;
}
2020-01-26 12:04:38 -05:00
void VirtualServer::registerInternalClient(std::shared_ptr<ConnectedClient> client) {
client->state = ConnectionState::CONNECTED;
2020-01-23 20:57:58 -05:00
{
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);
}
}
2020-01-26 12:04:38 -05:00
void VirtualServer::unregisterInternalClient(std::shared_ptr<ConnectedClient> client) {
client->state = ConnectionState::DISCONNECTED;
2020-01-23 20:57:58 -05:00
{
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();
}
}
}
}
2020-01-26 12:04:38 -05:00
bool VirtualServer::assignDefaultChannel(const shared_ptr<ConnectedClient>& client, bool join) {
2020-01-23 20:57:58 -05:00
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)) {
2020-01-23 20:57:58 -05:00
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()))) {
2020-01-23 20:57:58 -05:00
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
2019-11-23 15:16:55 -05:00
logMessage(this->serverId, "{} Client {}/{} tried to join on a not existing channel. Name: {}",
2020-01-23 20:57:58 -05:00
CLIENT_STR_LOG_PREFIX_(client),
client->getDisplayName(), client->getUid(),
client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>());
}
if(!channel) channel = this->channelTree->getDefaultChannel();
2020-01-23 20:57:58 -05:00
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;
}
2020-01-26 12:04:38 -05:00
void VirtualServer::testBanStateChange(const std::shared_ptr<ConnectedClient>& invoker) {
2020-01-23 20:57:58 -05:00
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);
2020-02-01 08:32:16 -05:00
client->close_connection(system_clock::now() + seconds(1));
2020-01-23 20:57:58 -05:00
}
});
}
2020-01-26 12:04:38 -05:00
bool VirtualServer::could_default_create_channel() {
2020-01-23 20:57:58 -05:00
{
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);
*/
2020-01-26 12:04:38 -05:00
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) {
2020-01-23 20:57:58 -05:00
/* 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;
}
2020-01-26 12:04:38 -05:00
void VirtualServer::notify_client_kick(
2020-01-23 20:57:58 -05:00
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) {
2020-01-23 20:57:58 -05:00
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!
*/
2020-06-28 08:01:14 -04:00
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, bool temp_delete) {
2020-01-23 20:57:58 -05:00
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();
2020-06-28 08:01:14 -04:00
auto deleted_channels = this->channelTree->delete_channel_root(channel);
log::ChannelDeleteReason delete_reason{temp_delete ? log::ChannelDeleteReason::EMPTY : log::ChannelDeleteReason::USER_ACTION};
for(const auto& deleted_channel : deleted_channels) {
serverInstance->action_logger()->channel_logger.log_channel_delete(this->serverId, invoker, deleted_channel->channelId(), channel == deleted_channel ? delete_reason : log::ChannelDeleteReason::PARENT_DELETED);
}
2020-01-23 20:57:58 -05:00
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
unique_lock client_channel_lock(client->channel_lock);
client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker);
});
}
2020-01-26 12:04:38 -05:00
void VirtualServer::client_move(
2020-01-23 20:57:58 -05:00
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) {
2020-01-23 20:57:58 -05:00
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);
2020-01-23 20:57:58 -05:00
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)) {
2020-01-23 20:57:58 -05:00
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));
}