404 lines
16 KiB
C++
404 lines
16 KiB
C++
#include <src/VirtualServer.h>
|
|
#include <misc/rnd.h>
|
|
#include <misc/digest.h>
|
|
#include <misc/base64.h>
|
|
#include <src/client/music/MusicClient.h>
|
|
#include <src/InstanceHandler.h>
|
|
#include <algorithm>
|
|
#include <log/LogUtils.h>
|
|
|
|
using namespace std;
|
|
using namespace std::chrono;
|
|
using namespace ts;
|
|
using namespace ts::server;
|
|
using namespace ts::music;
|
|
|
|
extern InstanceHandler* serverInstance;
|
|
threads::ThreadPool MusicBotManager::tick_music{config::threads::music::execute_per_bot, "music tick "};
|
|
threads::ThreadPool MusicBotManager::load_music{4, "music loader "};
|
|
|
|
void MusicBotManager::adjustTickPool() {
|
|
size_t bots = 0;
|
|
for(const auto& server : serverInstance->getVoiceServerManager()->serverInstances()) bots += server->music_manager_->current_bot_count();
|
|
if(bots == 0)
|
|
tick_music.setThreads(1);
|
|
else
|
|
tick_music.setThreads(min(config::threads::music::execute_limit, bots * config::threads::music::execute_per_bot));
|
|
}
|
|
|
|
void MusicBotManager::shutdown() {
|
|
tick_music.shutdown();
|
|
load_music.shutdown();
|
|
}
|
|
|
|
MusicBotManager::MusicBotManager(const shared_ptr<server::VirtualServer>& server) : handle(server) { }
|
|
|
|
MusicBotManager::~MusicBotManager() { }
|
|
|
|
void MusicBotManager::cleanup_semi_bots() {
|
|
for(const auto& bot : this->available_bots())
|
|
if(bot->get_bot_type() == MusicClient::Type::SEMI_PERMANENT || bot->get_bot_type() == MusicClient::Type::TEMPORARY)
|
|
this->deleteBot(bot);
|
|
}
|
|
|
|
void MusicBotManager::cleanup_client_bots(ts::ClientDbId clientid) {
|
|
for(const auto& bot : this->available_bots())
|
|
if(bot->get_bot_type() == MusicClient::Type::TEMPORARY && bot->getOwner() == clientid)
|
|
this->deleteBot(bot);
|
|
}
|
|
|
|
std::deque<std::shared_ptr<ts::server::MusicClient>> MusicBotManager::available_bots() {
|
|
lock_guard lock(music_bots_lock);
|
|
return this->music_bots;
|
|
}
|
|
|
|
std::shared_ptr<ts::server::MusicClient> MusicBotManager::find_bot_by_playlist(const std::shared_ptr<ts::music::PlayablePlaylist> &playlist) {
|
|
for(const auto& bot : this->available_bots())
|
|
if(bot->playlist() == playlist)
|
|
return bot;
|
|
return nullptr;
|
|
}
|
|
|
|
std::deque<std::shared_ptr<ts::server::MusicClient>> MusicBotManager::listBots(ClientDbId clid) {
|
|
lock_guard lock(music_bots_lock);
|
|
std::deque<std::shared_ptr<server::MusicClient>> res;
|
|
for(const auto& bot : this->music_bots)
|
|
if(bot->properties()[property::CLIENT_OWNER] == clid) res.push_back(bot);
|
|
return res;
|
|
}
|
|
|
|
std::shared_ptr<server::MusicClient> MusicBotManager::createBot(ClientDbId owner) {
|
|
if(!config::license->isPremium()) {
|
|
if(this->current_bot_count() >= this->max_bots()) return nullptr; //Test the license
|
|
}
|
|
|
|
auto handle = this->handle.lock();
|
|
assert(handle);
|
|
|
|
auto uid = base64::encode(digest::sha1("music#" + rnd_string(15)));
|
|
auto musicBot = make_shared<MusicClient>(this->handle.lock(), uid);
|
|
musicBot->_this = musicBot;
|
|
musicBot->manager = this;
|
|
musicBot->server = handle;
|
|
DatabaseHelper::assignDatabaseId(handle->getSql(), handle->getServerId(), musicBot);
|
|
if(config::music::enabled) {
|
|
lock_guard lock(this->music_bots_lock);
|
|
this->music_bots.push_back(musicBot);
|
|
}
|
|
(LOG_SQL_CMD)(sql::command(handle->getSql(), "INSERT INTO `musicbots` (`serverId`, `botId`, `uniqueId`, `owner`) VALUES (:sid, :botId, :uid, :owner)",
|
|
variable{":sid", handle->getServerId()}, variable{":botId", musicBot->getClientDatabaseId()}, variable{":uid", musicBot->getUid()}, variable{":owner", owner}).execute());
|
|
musicBot->properties()[property::CLIENT_OWNER] = owner;
|
|
musicBot->setDisplayName("Im a music bot!");
|
|
musicBot->properties()[property::CLIENT_LASTCONNECTED] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
|
musicBot->properties()[property::CLIENT_CREATED] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
|
musicBot->properties()[property::CLIENT_VERSION] = "TeaMusic";
|
|
musicBot->properties()[property::CLIENT_PLATFORM] = "internal";
|
|
|
|
|
|
if(!config::music::enabled) return nullptr;
|
|
handle->groups->enableCache(musicBot->getClientDatabaseId());
|
|
handle->registerClient(musicBot);
|
|
|
|
{
|
|
auto playlist = this->create_playlist(owner, "owned via bot");
|
|
playlist->properties()[property::PLAYLIST_TYPE] = Playlist::Type::BOT_BOUND;
|
|
musicBot->set_playlist(playlist);
|
|
}
|
|
|
|
MusicBotManager::adjustTickPool();
|
|
return musicBot;
|
|
}
|
|
|
|
bool MusicBotManager::assign_playlist(const std::shared_ptr<ts::server::MusicClient> &bot, const std::shared_ptr<ts::music::PlayablePlaylist> &playlist) {
|
|
lock_guard lock(music_bots_lock);
|
|
|
|
if(playlist) {
|
|
auto assigned_bot = this->find_bot_by_playlist(playlist);
|
|
if(assigned_bot)
|
|
return assigned_bot == bot;
|
|
}
|
|
|
|
auto old_playlist = bot->playlist();
|
|
bot->set_playlist(playlist);
|
|
|
|
if(old_playlist && old_playlist->playlist_type() == Playlist::Type::BOT_BOUND) {
|
|
string error;
|
|
if(!this->delete_playlist(old_playlist->playlist_id(), error)) {
|
|
logError(this->ref_server()->getServerId(), "Failed to delete music bot bound playlist. Error: {}", error);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MusicBotManager::deleteBot(std::shared_ptr<server::MusicClient> musicBot) {
|
|
{
|
|
lock_guard lock(this->music_bots_lock);
|
|
auto found = find(this->music_bots.begin(), this->music_bots.end(), musicBot);
|
|
if(found == this->music_bots.end()) return;
|
|
this->music_bots.erase(found);
|
|
}
|
|
auto handle = this->handle.lock();
|
|
assert(handle);
|
|
|
|
MusicBotManager::adjustTickPool();
|
|
{
|
|
unique_lock server_channel_lock(handle->channel_tree_lock);
|
|
handle->client_move(musicBot, nullptr, nullptr, "Music bot deleted", ViewReasonId::VREASON_SERVER_LEFT, true, server_channel_lock);
|
|
handle->unregisterClient(musicBot, "bot deleted", server_channel_lock);
|
|
}
|
|
handle->groups->disableCache(musicBot->getClientDatabaseId());
|
|
serverInstance->databaseHelper()->deleteClient(handle, musicBot->getClientDatabaseId());
|
|
serverInstance->databaseHelper()->deleteClient(nullptr, musicBot->getClientDatabaseId());
|
|
|
|
this->assign_playlist(musicBot, nullptr); /* remove any playlists */
|
|
/*
|
|
auto playlist = musicBot->playlist();
|
|
if(playlist && playlist->playlist_type() == Playlist::Type::BOT_BOUND) {
|
|
string error;
|
|
if(!this->delete_playlist(playlist->playlist_id(), error)) {
|
|
logError(this->ref_server()->getServerId(), "Failed to delete music bot bound playlist. Error: {}", error);
|
|
}
|
|
}
|
|
*/
|
|
|
|
sql::command(handle->getSql(), "DELETE FROM `musicbots` WHERE `serverId` = :sid AND `botId` = :bid", variable{":sid", handle->getServerId()}, variable{":bid", musicBot->getClientDatabaseId()}).executeLater().waitAndGetLater(LOG_SQL_CMD,{-1,"future failed"});
|
|
|
|
std::thread([musicBot]{
|
|
musicBot->player_reset(false);
|
|
}).detach();
|
|
}
|
|
|
|
int MusicBotManager::max_bots() {
|
|
int bots = this->handle.lock()->properties()[property::VIRTUALSERVER_MUSIC_BOT_LIMIT];
|
|
if(!config::license->isPremium())
|
|
return bots == 0 ? 0 : 1;
|
|
return bots;
|
|
}
|
|
|
|
int MusicBotManager::current_bot_count() {
|
|
return this->music_bots.size();
|
|
}
|
|
|
|
std::shared_ptr<server::MusicClient> MusicBotManager::findBotById(ClientDbId id) {
|
|
lock_guard lock(music_bots_lock);
|
|
for(const auto& bot : this->music_bots)
|
|
if(bot->getClientDatabaseId() == id) return bot;
|
|
return nullptr;
|
|
}
|
|
|
|
// CREATE_TABLE("musicbots", "`serverId` INT, `botId` INT, `uniqueId` TEXT, `owner` INT");
|
|
void MusicBotManager::load_bots() {
|
|
if(!config::music::enabled) return;
|
|
|
|
auto handle = this->handle.lock();
|
|
assert(handle);
|
|
|
|
auto res = sql::command(handle->getSql(), "SELECT `botId`, `uniqueId`, `owner` FROM `musicbots` WHERE `serverId` = :sid", variable{":sid", handle->getServerId()}).query(&MusicBotManager::sqlCreateMusicBot, this);
|
|
(LOG_SQL_CMD)(res);
|
|
MusicBotManager::adjustTickPool();
|
|
|
|
auto bot_limit = this->max_bots();
|
|
if(bot_limit >= 0) {
|
|
size_t disabled_bots = 0;
|
|
for(const auto& bot : this->music_bots) {
|
|
if(bot_limit >= 1) {
|
|
if(!bot->properties()[property::CLIENT_DISABLED].as<bool>())
|
|
bot_limit--;
|
|
} else {
|
|
//TODO log message
|
|
bot->properties()[property::CLIENT_DISABLED] = true;
|
|
disabled_bots++;
|
|
}
|
|
}
|
|
if(disabled_bots > 0){
|
|
logMessage(handle->getServerId(), "[Music] Disabled {} music bots, because they exceed tha max music bot limit", disabled_bots);
|
|
}
|
|
}
|
|
}
|
|
|
|
int MusicBotManager::sqlCreateMusicBot(int length, std::string* values, std::string* names) {
|
|
auto handle = this->handle.lock();
|
|
assert(handle);
|
|
|
|
ClientDbId botId = 0, owner = 0;
|
|
std::string uid;
|
|
for(int index = 0; index < length; index++)
|
|
if(names[index] == "botId")
|
|
botId = stoll(values[index]);
|
|
else if(names[index] == "uniqueId")
|
|
uid = values[index];
|
|
else if(names[index] == "owner")
|
|
owner = stoll(values[index]);
|
|
if(botId == 0 || uid.empty()) return 0;
|
|
|
|
auto musicBot = make_shared<MusicClient>(handle, uid);
|
|
musicBot->_this = musicBot;
|
|
musicBot->manager = this;
|
|
DatabaseHelper::assignDatabaseId(handle->getSql(), handle->getServerId(), musicBot);
|
|
musicBot->properties()[property::CLIENT_OWNER] = owner;
|
|
if(musicBot->properties()[property::CLIENT_UPTIME_MODE] == MusicClient::UptimeMode::TIME_SINCE_SERVER_START) {
|
|
musicBot->properties()[property::CLIENT_LASTCONNECTED] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
handle->groups->enableCache(musicBot->getClientDatabaseId());
|
|
if(musicBot->getClientDatabaseId() != botId) logCritical(handle->getServerId(),"Invalid music bot id mapping!");
|
|
|
|
{
|
|
auto playlist = this->find_playlist(musicBot->properties()[property::CLIENT_PLAYLIST_ID]);
|
|
if(!playlist) {
|
|
debugMessage(this->ref_server()->getServerId(), "Bot {} hasn't a valid playlist ({}). Creating a new one", musicBot->getClientDatabaseId(), musicBot->properties()[property::CLIENT_PLAYLIST_ID].value());
|
|
playlist = this->create_playlist(0, "Music Manager");
|
|
playlist->properties()[property::PLAYLIST_TYPE] = Playlist::Type::BOT_BOUND;
|
|
}
|
|
playlist->load_songs();
|
|
musicBot->set_playlist(playlist);
|
|
}
|
|
|
|
{
|
|
lock_guard lock(this->music_bots_lock);
|
|
this->music_bots.push_back(musicBot);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MusicBotManager::connectBots() {
|
|
for(const auto& bot : this->music_bots) {
|
|
bot->server = this->handle.lock();
|
|
if(!bot->currentChannel || bot->getClientId() == 0)
|
|
bot->initialize_bot();
|
|
}
|
|
}
|
|
|
|
void MusicBotManager::disconnectBots() {
|
|
auto handle = this->handle.lock();
|
|
assert(handle);
|
|
|
|
for(const auto& bot : this->music_bots) {
|
|
if(bot->currentChannel) {
|
|
unique_lock server_channel_lock(handle->channel_tree_lock);
|
|
handle->client_move(bot, nullptr, nullptr, "Music bot deleted", ViewReasonId::VREASON_SERVER_LEFT, true, server_channel_lock);
|
|
}
|
|
bot->server = nullptr;
|
|
}
|
|
}
|
|
|
|
void MusicBotManager::load_playlists() {
|
|
if(!config::music::enabled) return;
|
|
|
|
lock_guard playlist_lock(this->playlists_lock);
|
|
auto sql_result = sql::command(this->ref_server()->getSql(), "SELECT `playlist_id` FROM `playlists` WHERE `serverId` = :server_id", variable{":server_id", this->ref_server()->getServerId()}).query([&](int length, string* values, string* names){
|
|
if(length != 1) return;
|
|
|
|
PlaylistId playlist_id = 0;
|
|
try {
|
|
playlist_id = stoll(values[0]);
|
|
} catch(const std::exception& ex) {
|
|
logError(this->ref_server()->getServerId(), "Failed to parse playlist id from database. ID: {}", values[0]);
|
|
return;
|
|
}
|
|
|
|
for(const auto& playlist : this->playlists_list) {
|
|
if(playlist->playlist_id() == playlist_id) {
|
|
logError(this->ref_server()->getServerId(), "Duplicated playlist it's. ({})", playlist_id);
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto properties = serverInstance->databaseHelper()->loadPlaylistProperties(this->ref_server(), playlist_id);
|
|
auto permissions = serverInstance->databaseHelper()->loadPlaylistPermissions(this->ref_server(), playlist_id);
|
|
auto playlist = make_shared<PlayablePlaylist>(this->ref(), properties, permissions);
|
|
playlist->_self = playlist;
|
|
playlist->load_songs();
|
|
this->playlists_list.push_back(playlist);
|
|
|
|
if(playlist->playlist_id() > this->playlists_index)
|
|
this->playlists_index = playlist->playlist_id();
|
|
});
|
|
LOG_SQL_CMD(sql_result);
|
|
}
|
|
|
|
std::shared_ptr<PlayablePlaylist> MusicBotManager::find_playlist(ts::PlaylistId id) {
|
|
lock_guard lock(this->playlists_lock);
|
|
for(const auto& playlist : this->playlists_list)
|
|
if(playlist->playlist_id() == id)
|
|
return playlist;
|
|
return nullptr;
|
|
}
|
|
|
|
std::deque<std::shared_ptr<PlayablePlaylist>> MusicBotManager::find_playlists_by_client(ClientDbId client_dbid) {
|
|
std::deque<std::shared_ptr<PlayablePlaylist>> result;
|
|
|
|
{
|
|
lock_guard lock(this->playlists_lock);
|
|
for(const auto& playlist : this->playlists_list)
|
|
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] == client_dbid)
|
|
result.push_back(playlist);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//playlists => "`serverId` INT NOT NULL, `playlist_id` INT"
|
|
std::shared_ptr<PlayablePlaylist> MusicBotManager::create_playlist(ts::ClientDbId owner_id, const std::string &owner_name) {
|
|
auto playlist_id = ++this->playlists_index;
|
|
auto sql_result = sql::command(this->ref_server()->getSql(), "INSERT INTO `playlists` (`serverId`, `playlist_id`) VALUES (:server_id, :playlist_id)",
|
|
variable{":server_id", this->ref_server()->getServerId()},
|
|
variable{":playlist_id", playlist_id}
|
|
).execute();
|
|
LOG_SQL_CMD(sql_result);
|
|
|
|
if(!sql_result)
|
|
return nullptr;
|
|
|
|
auto properties = serverInstance->databaseHelper()->loadPlaylistProperties(this->ref_server(), playlist_id);
|
|
auto permissions = serverInstance->databaseHelper()->loadPlaylistPermissions(this->ref_server(), playlist_id);
|
|
auto playlist = make_shared<PlayablePlaylist>(this->ref(), properties, permissions);
|
|
playlist->_self = playlist;
|
|
playlist->load_songs();
|
|
{
|
|
lock_guard playlist_lock(this->playlists_lock);
|
|
this->playlists_list.push_back(playlist);
|
|
}
|
|
playlist->properties()[property::PLAYLIST_OWNER_DBID] = owner_id;
|
|
playlist->properties()[property::PLAYLIST_OWNER_NAME] = owner_name;
|
|
|
|
return playlist;
|
|
}
|
|
|
|
bool MusicBotManager::delete_playlist(ts::PlaylistId id, std::string &error) {
|
|
unique_lock playlist_lock(this->playlists_lock);
|
|
auto playlist_entry = this->find_playlist(id);
|
|
|
|
for(const auto& bot : this->available_bots()) {
|
|
if(bot->playlist() == playlist_entry) {
|
|
error = "bot " + to_string(bot->getClientDatabaseId()) + " is using this playlist";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto it = find(this->playlists_list.begin(), this->playlists_list.end(), playlist_entry);
|
|
if(it != this->playlists_list.end())
|
|
this->playlists_list.erase(it);
|
|
playlist_lock.unlock();
|
|
|
|
if(!serverInstance->databaseHelper()->deletePlaylist(this->ref_server(), id)) {
|
|
error = "database deletion failed";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void MusicBotManager::execute_tick() {
|
|
auto vs = this->handle.lock();
|
|
if(!vs) return;
|
|
|
|
unique_lock playlist_lock(this->playlists_lock);
|
|
auto playlists = this->playlists_list;
|
|
playlist_lock.unlock();
|
|
|
|
auto db_helper = serverInstance->databaseHelper();
|
|
for(auto& playlist : playlists)
|
|
db_helper->savePlaylistPermissions(vs, playlist->playlist_id(), playlist->permission_manager());
|
|
} |