#include #include #include #include #include #include using namespace std; using namespace std::chrono; using namespace ts; using namespace ts::server; using namespace ts::permission; //#define DISABLE_CACHING DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {} DatabaseHelper::~DatabaseHelper() { for(const auto& elm : cachedPermissionManagers) delete elm; cachedPermissionManagers.clear(); } void DatabaseHelper::tick() { { threads::MutexLock l(this->permManagerLock); auto cpy = this->cachedPermissionManagers; for(const auto& mgr : cpy){ //if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) //TODO remove instand delete! mgr->ownLock.reset(); if(mgr->manager.expired()){ this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), mgr)); delete mgr; } } } { threads::MutexLock l(this->propsLock); auto pcpy = this->cachedProperties; for(const auto& mgr : pcpy){ if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) mgr->ownLock.reset(); if(mgr->properties.expired()) { this->cachedProperties.erase(std::find(this->cachedProperties.begin(), this->cachedProperties.end(), mgr)); delete mgr; } } } } int collectData(deque>* list, int length, char** values, char** columns){ shared_ptr entry = std::make_shared(); for(int index = 0; index < length; index++) if(strcmp(columns[index], "cldbid") == 0) entry->cldbid = static_cast(stol(values[index])); else if(strcmp(columns[index], "clientUid") == 0) entry->uniqueId = values[index]; else if(strcmp(columns[index], "firstConnect") == 0) entry->created = time_point() + seconds(stoll(values[index])); else if(strcmp(columns[index], "lastConnect") == 0) entry->lastjoin = time_point() + seconds(stoll(values[index])); else if(strcmp(columns[index], "connections") == 0) entry->connections = static_cast(stoi(values[index])); else if(strcmp(columns[index], "lastName") == 0) entry->lastName = values[index]; else if(strcmp(columns[index], "serverId") == 0); else logError("Invalid db key for manager data. Key: " + string(columns[index])); list->push_back(entry); return 0; } #define MAX_QUERY 32 std::deque> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr& server, const std::deque& list) { if(list.empty()) return {}; deque> result; if(list.size() <= MAX_QUERY){ std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND ("; for(auto elm : list) query += " `cldbid` = " + to_string(elm) + " OR"; query = query.substr(0, query.length() - 3) + ")"; logTrace("[SQL] queryDatabseInfo(...) -> " + query); auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(std::function(collectData), &result); auto pf = LOG_SQL_CMD; pf(state); if(!state) return {}; } else { std::deque sub; do { sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY)); list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY)); auto res = this->queryDatabaseInfo(server, sub); result.insert(result.end(), res.begin(), res.end()); sub.clear(); } while(!list.empty()); } return result; } std::deque> DatabaseHelper::queryDatabaseInfoByUid(const std::shared_ptr &server, std::deque list) { if(list.empty()) return {}; deque> result; if(list.size() <= MAX_QUERY){ std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND ("; for(const auto &elm : list) query += " `clientUid` = '" + elm + "' OR"; query = query.substr(0, query.length() - 3) + ")"; logTrace("[SQL] queryDatabseInfoByUid(...) -> " + query); auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(function(collectData), &result); auto pf = LOG_SQL_CMD; pf(state); if(!state) return {}; } else { std::deque sub; do { sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY)); list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY)); auto res = this->queryDatabaseInfoByUid(server, sub); result.insert(result.end(), res.begin(), res.end()); sub.clear(); } while(!list.empty()); } return result; } bool DatabaseHelper::validClientDatabaseId(const std::shared_ptr& server, ClientDbId cldbid) { return cldbid > 0; } //TODO here check void DatabaseHelper::deleteClient(const std::shared_ptr& server, ClientDbId cldbid) { ServerId sid = static_cast(server ? server->getServerId() : 0); { lock_guard lock(permManagerLock); for(auto permMgr : this->cachedPermissionManagers) if(permMgr->cldbid == cldbid && permMgr->sid == sid) { this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr)); delete permMgr; break; } } //TODO remove from props cache? auto state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", sid}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute(); state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", sid}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute(); state = sql::command(this->sql, "DELETE FROM `clients` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute(); state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute(); state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute(); //TODO delete letters //TODO delete query //TODO delete complains } struct PermissionArguments { sql::command& command; PermissionManager* manager; TSServer* server; inline int serverId() { return this->server ? this->server->getServerId() : 0; } }; inline sql::result load_permissions(const std::shared_ptr& server, PermissionManager* manager, sql::command& command) { auto start = system_clock::now(); auto data = PermissionArguments{command, manager, server.get()}; return command.query([](PermissionArguments* data, int length, char** values, char** names){ permission::PermissionType key = permission::PermissionType::undefined; permission::PermissionValue value = 0, granted = 0; bool negated = false, skipped = false; std::shared_ptr channel; int index; try { for(index = 0; index < length; index++) { if(strcmp(names[index], "permId") == 0) { key = permission::resolvePermissionData(values[index])->type; if(key == permission::unknown){ debugMessage(data->serverId(), "[SQL] Permission entry contains invalid permission type! Type: {} Command: {}", values[index], data->command.sqlCommand()); return 0; } if(key == permission::undefined){ debugMessage(data->serverId(), "[SQL] Permission entry contains undefined permission type! Type: {} Command: {}", values[index], data->command.sqlCommand()); return 0; } } else if(strcmp(names[index], "channelId") == 0) { auto channelId = stoull(values[index]); if(channelId > 0) { if(!data->server) logError(data->serverId(), "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", data->command.sqlCommand(), values[index]); else { channel = data->server->getChannelTree()->findChannel(channelId); if(!channel) logError(data->serverId(), "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", data->command.sqlCommand(), values[index]); } } } else if(strcmp(names[index], "value") == 0) { value = stoi(values[index]); } else if(strcmp(names[index], "grant") == 0) { granted = stoi(values[index]); } else if(strcmp(names[index], "flag_skip") == 0) skipped = strcmp(values[index], "1") == 0; else if(strcmp(names[index], "flag_negate") == 0) negated = strcmp(values[index], "1") == 0; } } catch(std::exception& ex) { logError(data->serverId(), "[SQL] Cant load permissions! Failed to parse value! Command: {} Message: {} Key: {} Value: {}", data->command.sqlCommand(), ex.what(),names[index], values[index]); return 0; } if(key == permission::undefined) { debugMessage(data->serverId(), "[SQL] Permission entry misses permission type! Command: {}", data->command.sqlCommand()); return 0; } auto permission = data->manager->registerPermission(key, value, channel); permission->granted = granted; permission->value = value; permission->dbReference = true; permission->flag_negate = negated; permission->flag_skip = skipped; return 0; }, &data); auto end = system_clock::now(); auto time = end - start; logTrace(server ? server->getServerId() : 0, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast(time).count()); } inline sql::result load_permissions_v2(const std::shared_ptr& server, v2::PermissionManager* manager, sql::command& command, bool resolve_channel /* only used for client permissions (client channel permissions) */) { auto start = system_clock::now(); auto server_id = server ? server->getServerId() : 0; return command.query([&](int length, char** values, char** names){ permission::PermissionType key = permission::PermissionType::undefined; permission::PermissionValue value = 0, granted = 0; bool negated = false, skipped = false; std::shared_ptr channel; int index; try { for(index = 0; index < length; index++) { if(strcmp(names[index], "permId") == 0) { key = permission::resolvePermissionData(values[index])->type; if(key == permission::unknown){ debugMessage(server_id, "[SQL] Permission entry contains invalid permission type! Type: {} Command: {}", values[index], command.sqlCommand()); return 0; } if(key == permission::undefined){ debugMessage(server_id, "[SQL] Permission entry contains undefined permission type! Type: {} Command: {}", values[index], command.sqlCommand()); return 0; } } else if(strcmp(names[index], "channelId") == 0) { if(resolve_channel) { auto channelId = stoull(values[index]); if(channelId > 0) { if(!server) logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]); else { channel = server->getChannelTree()->findChannel(channelId); if(!channel) logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]); } } } } else if(strcmp(names[index], "value") == 0) { value = stoi(values[index]); } else if(strcmp(names[index], "grant") == 0) { granted = stoi(values[index]); } else if(strcmp(names[index], "flag_skip") == 0) skipped = strcmp(values[index], "1") == 0; else if(strcmp(names[index], "flag_negate") == 0) negated = strcmp(values[index], "1") == 0; } } catch(std::exception& ex) { logError(server_id, "[SQL] Cant load permissions! Failed to parse value! Command: {} Message: {} Key: {} Value: {}", command.sqlCommand(), ex.what(),names[index], values[index]); return 0; } if(key == permission::undefined) { debugMessage(server_id, "[SQL] Permission entry misses permission type! Command: {}", command.sqlCommand()); return 0; } if(!resolve_channel || !channel) manager->load_permission(key, {value, granted}, negated, skipped, value != permNotGranted, granted != permNotGranted); else manager->load_permission(key, {value, granted}, channel->channelId(), negated, skipped, value != permNotGranted, granted != permNotGranted); return 0; }); auto end = system_clock::now(); auto time = end - start; logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast(time).count()); } #define UPDATE_COMMAND "UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId" #define INSERT_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)" #define DELETE_COMMAND "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId" std::shared_ptr DatabaseHelper::loadClientPermissionManager(const std::shared_ptr& server, ClientDbId cldbid) { auto server_id = server ? server->getServerId() : 0; #ifndef DISABLE_CACHING { lock_guard lock(permManagerLock); for(auto permMgr : this->cachedPermissionManagers) if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)){ auto ptr = permMgr->manager.lock(); if(!ptr){ this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr)); delete permMgr; break; } permMgr->lastAccess = system_clock::now(); return ptr; } } #endif logTrace(server_id, "[Permission] Loading client permission manager for client {}", cldbid); auto pMgr = std::make_shared(); bool loaded = false; if(this->use_startup_cache && server) { shared_ptr entry; { threads::MutexLock lock(this->startup_lock); for(const auto& entries : this->startup_entries) { if(entries->sid == server->getServerId()) { entry = entries; break; } } } if(entry) { for(const auto& perm : entry->permissions) { if(perm->type == permission::SQL_PERM_USER && perm->id == cldbid) { auto channel = perm->channelId > 0 ? server->getChannelTree()->findChannel(perm->channelId) : nullptr; if(channel) pMgr->load_permission(perm->permission->type, {perm->value, perm->grant}, channel->channelId(), perm->flag_negate, perm->flag_skip, perm->value != permNotGranted, perm->grant != permNotGranted); else pMgr->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_negate, perm->flag_skip, perm->value != permNotGranted, perm->grant != permNotGranted); } } loaded = true; } } if(!loaded) { auto command = sql::command(this->sql, "SELECT `permId`, `value`, `channelId`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}); LOG_SQL_CMD(load_permissions_v2(server, pMgr.get(), command, true)); } #ifndef DISABLE_CACHING this->permManagerLock.lock(); auto entry = new CachedPermissionManager(); entry->sid = server_id; entry->manager = pMgr; entry->ownLock = pMgr; entry->cldbid = cldbid; entry->lastAccess = system_clock::now(); this->cachedPermissionManagers.push_back(entry); this->permManagerLock.unlock(); #endif return pMgr; } void DatabaseHelper::saveClientPermissions(const std::shared_ptr &server, ts::ClientDbId client_dbid, const std::shared_ptr &permissions) { const auto updates = permissions->flush_db_updates(); if(updates.empty()) return; auto server_id = server ? server->getServerId() : 0; for(auto& update : updates) { std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND); auto permission_data = permission::resolvePermissionData(update.permission); logTrace(server_id, "[CHANNEL] Updating client permission for client {}: {}. New value: {}. New grant: {}. Query: {}", client_dbid, permission_data->name, update.values.value, update.values.grant, query ); sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":id", client_dbid}, variable{":chId", 0}, variable{":type", permission::SQL_PERM_USER}, variable{":permId", permission_data->name}, variable{":value", update.values.value}, variable{":grant", update.values.grant}, variable{":flag_skip", update.flag_skip}, variable{":flag_negate", update.flag_negate}) .executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future error"}); } } std::shared_ptr DatabaseHelper::loadGroupPermissions(const std::shared_ptr& server, ts::GroupId group_id) { auto result = std::make_shared(); if(this->use_startup_cache && server) { shared_ptr entry; { threads::MutexLock lock(this->startup_lock); for(const auto& entries : this->startup_entries) { if(entries->sid == server->getServerId()) { entry = entries; break; } } } if(entry) { for(const auto& perm : entry->permissions) { if(perm->type == permission::SQL_PERM_GROUP && perm->id == group_id) { result->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_negate, perm->flag_skip, perm->value != permNotGranted, perm->grant != permNotGranted); } } return result; } } //7931 auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", permission::SQL_PERM_GROUP}, variable{":id", group_id}); LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false)); return result; } void DatabaseHelper::saveGroupPermissions(const std::shared_ptr &server, ts::GroupId group_id, const std::shared_ptr &permissions) { const auto updates = permissions->flush_db_updates(); if(updates.empty()) return; auto server_id = server ? server->getServerId() : 0; for(auto& update : updates) { std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND); auto permission_data = permission::resolvePermissionData(update.permission); logTrace(server_id, "[CHANNEL] Updating group permission for group {}: {}. New value: {}. New grant: {}. Query: {}", group_id, permission_data->name, update.values.value, update.values.grant, query ); sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":id", group_id}, variable{":chId", 0}, variable{":type", permission::SQL_PERM_GROUP}, variable{":permId", permission_data->name}, variable{":value", update.values.value}, variable{":grant", update.values.grant}, variable{":flag_skip", update.flag_skip}, variable{":flag_negate", update.flag_negate}) .executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future error"}); } } std::shared_ptr DatabaseHelper::loadPlaylistPermissions(const std::shared_ptr &server, ts::PlaylistId playlist_id) { shared_ptr result; if(this->use_startup_cache && server) { shared_ptr entry; { threads::MutexLock lock(this->startup_lock); for(const auto& entries : this->startup_entries) { if(entries->sid == server->getServerId()) { entry = entries; break; } } } if(entry) { result = std::make_shared(); for(const auto& perm : entry->permissions) { if(perm->type == permission::SQL_PERM_PLAYLIST && perm->id == playlist_id) { auto permission = result->registerPermission(perm->permission, perm->value, server->getChannelTree()->findChannel(perm->channelId)); permission->granted = perm->grant; permission->value = perm->value; permission->dbReference = true; permission->flag_negate = perm->flag_negate; permission->flag_skip = perm->flag_skip; } } } } if(!result) { result = std::make_shared(); auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", permission::SQL_PERM_PLAYLIST}, variable{":id", playlist_id}); LOG_SQL_CMD(load_permissions(server, result.get(), command)); } assert(result); weak_ptr weak_server = server; auto server_id = server ? server->getServerId() : 0; result->registerUpdateHandler([&, weak_server, server_id, playlist_id](std::shared_ptr permission) { auto server = weak_server.lock(); if(!server && server_id != 0) { logError(server_id, "Tried to update a playlist permission of a expired server!"); return; } std::string query; /* if(permission->deleted){ query = "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `id` = :id AND `type`= :type AND `permId`= :permId AND `channelId` = :chId"; } else */ if(permission->dbReference){ query = UPDATE_COMMAND; } else { permission->dbReference = true; query = INSERT_COMMAND; } logTrace(server ? server->getServerId() : 0, "[SQL] Executing group permission command: {}", query); sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":id", playlist_id}, variable{":type", permission::SQL_PERM_PLAYLIST}, variable{":chId", permission->channelId()}, variable{":permId", permission->type->name}, variable{":value", permission->value}, variable{":grant", permission->granted}, variable{":flag_skip", permission->flag_skip}, variable{":flag_negate", permission->flag_negate}) .executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to save playlist permission " + to_string(playlist_id)}); }); return result; } std::shared_ptr DatabaseHelper::loadChannelPermissions(const std::shared_ptr& server, ts::ChannelId channel) { auto result = std::make_shared(); if(this->use_startup_cache && server) { shared_ptr entry; { threads::MutexLock lock(this->startup_lock); for(const auto& entries : this->startup_entries) { if(entries->sid == server->getServerId()) { entry = entries; break; } } } if(entry) { for(const auto& perm : entry->permissions) { if(perm->type == permission::SQL_PERM_CHANNEL && perm->channelId == channel) { result->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_negate, perm->flag_skip, perm->value != permNotGranted, perm->grant != permNotGranted); } } return result; } } auto command = sql::command(sql, "SELECT `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `channelId` = :chid AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":chid", channel}, variable{":id", 0}, variable{":type", permission::SQL_PERM_CHANNEL}); LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false)); return result; } void DatabaseHelper::saveChannelPermissions(const std::shared_ptr &server, ts::ChannelId channel_id, const std::shared_ptr &permissions) { const auto updates = permissions->flush_db_updates(); if(updates.empty()) return; auto server_id = server ? server->getServerId() : 0; for(auto& update : updates) { std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND); auto permission_data = permission::resolvePermissionData(update.permission); logTrace(server_id, "[CHANNEL] Updating channel permission for channel {}: {}. New value: {}. New grant: {}. Query: {}", channel_id, permission_data->name, update.values.value, update.values.grant, query ); sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":id", 0}, variable{":chId", channel_id}, variable{":type", permission::SQL_PERM_CHANNEL}, variable{":permId", permission_data->name}, variable{":value", update.values.value}, variable{":grant", update.values.grant}, variable{":flag_skip", update.flag_skip}, variable{":flag_negate", update.flag_negate}) .executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future error"}); } } #define DBSAVE_MUSIC_BOT (type == CLIENT_MUSIC ? PROP_DB_SAVE : 0) void DatabaseHelper::assign_default_properties_client(Properties *properties, ClientType type){ Properties& _properties = *properties; _properties.register_property_type(); _properties.register_property_type(); if(type == ClientType::CLIENT_MUSIC){ //_properties.registerProperty("channel_last", 0, PROP_DB_SAVE); //_properties.registerProperty("bot_owner", 0, PROP_TEMP); //saved in bot table //_properties.registerProperty("music_volume", 1.f, PROP_DB_SAVE); //_properties.registerProperty("music_track_id", 0, PROP_CLIENT_VARIABLE | PROP_CLIENT_VIEW_INFO | PROP_NEW); //_properties.registerProperty("music_player_state", 0, PROP_CLIENT_VARIABLE | PROP_CLIENT_VIEW_INFO | PROP_NEW); _properties[property::CLIENT_INPUT_HARDWARE] = true; _properties[property::CLIENT_OUTPUT_HARDWARE] = true; } else if(type == ClientType::CLIENT_QUERY) { _properties[property::CLIENT_INPUT_HARDWARE] = true; _properties[property::CLIENT_OUTPUT_HARDWARE] = true; } /* _properties.registerProperty("client_type", type, PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_type_exact", type, PROP_CLIENT_VIEW_INFO); _properties.registerProperty("clid", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO | PROP_CLIENT_VARIABLE); _properties.registerProperty("client_database_id", 0, PROP_CLIENT_VIEW_INFO); _properties.registerProperty("hwid", "", PROP_PRIVATE | PROP_DB_SAVE); _properties.registerProperty("connection_client_ip", "undefined", PROP_PRIVATE | PROP_DB_SAVE | PROP_SNAPSHOT); _properties.registerProperty("client_version", "unknown", PROP_CLIENT_VARIABLE | PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE | PROP_SNAPSHOT); _properties.registerProperty("client_platform", "unknown", PROP_CLIENT_VARIABLE | PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE | PROP_SNAPSHOT); _properties.registerProperty("client_login_name", "", PROP_CLIENT_VARIABLE); _properties.registerProperty("client_created", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE | DBSAVE_MUSIC_BOT | PROP_SNAPSHOT); _properties.registerProperty("client_lastconnected", 0, PROP_SERVER_BOUND | DBSAVE_MUSIC_BOT | PROP_CLIENT_VARIABLE | PROP_SNAPSHOT); _properties.registerProperty("client_totalconnections", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE | PROP_CLIENT_VIEW_INFO | PROP_SNAPSHOT); _properties.registerProperty("client_month_bytes_uploaded", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE); _properties.registerProperty("client_month_bytes_downloaded", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE); _properties.registerProperty("client_total_bytes_uploaded", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE); _properties.registerProperty("client_total_bytes_downloaded", 0, PROP_SERVER_BOUND | PROP_CLIENT_VARIABLE); _properties.registerProperty("connection_server2client_packetloss_keepalive", 0, PROP_SERVER_BOUND); _properties.registerProperty("connection_server2client_packetloss_control", 0, PROP_SERVER_BOUND); _properties.registerProperty("connection_server2client_packetloss_speech", 0, PROP_SERVER_BOUND); _properties.registerProperty("connection_server2client_packetloss_total", 0, PROP_SERVER_BOUND); _properties.registerProperty("client_unique_identifier", "", PROP_CLIENT_VIEW_INFO | PROP_SNAPSHOT); _properties.registerProperty("client_nickname", "", PROP_CLIENT_VIEW_INFO | DBSAVE_MUSIC_BOT | PROP_SNAPSHOT); _properties.registerProperty("client_nickname_phonetic", "", PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_input_muted", false, PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_output_muted", false, PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_outputonly_muted", false, PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_input_hardware", false, PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_output_hardware", false, PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_is_recording", false, PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_meta_data", "", PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); //unused _properties.registerProperty("client_channel_group_id", "0", PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_servergroups", "0", PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_away", false, PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_away_message", "", PROP_CLIENT_VIEW_INFO);CLIENT_DESCRIPTION _properties.registerProperty("client_flag_avatar", "", PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE); //Some random uuid. File id or stuff like that? _properties.registerProperty("client_description", ts::config::messages::defaultClientDescription, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE | PROP_SNAPSHOT); _properties.registerProperty("client_talk_power", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_talk_request", false, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_talk_request_msg", "", PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_is_talker", false, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_is_priority_speaker", false, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_is_channel_commander", false, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_unread_messages", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_needed_serverquery_view_power", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_icon_id", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE); _properties.registerProperty("client_country", ts::config::geo::countryFlag, PROP_CLIENT_VIEW_INFO); //Country shortcuts _properties.registerProperty("client_channel_group_inherited_channel_id", 0, PROP_SERVER_BOUND | PROP_CLIENT_VIEW_INFO); _properties.registerProperty("client_badges", "", PROP_CLIENT_VIEW_INFO | PROP_DB_SAVE); _properties.registerProperty("client_teaforum_id", 0, PROP_CLIENT_VIEW_INFO | PROP_NEW); _properties.registerProperty("client_teaforum_name", "", PROP_CLIENT_VIEW_INFO | PROP_NEW); //Client default channel settings _properties.registerProperty("client_default_channel", "", PROP_PRIVATE); _properties.registerProperty("client_default_channel_password", "", PROP_PRIVATE); */ } bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::shared_ptr cl) { cl->loadDataForCurrentServer(); if(cl->getClientDatabaseId() == 0){ //Client does not exist ClientDbId cldbid = 0; auto res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 AND `clientUid` = :cluid", variable{":cluid", cl->getUid()}).query([](ClientDbId* ptr, int length, char** values, char** names){ *ptr = static_cast(stoll(values[0])); return 0; }, &cldbid); auto pf = LOG_SQL_CMD; pf(res); if(!res) return false; auto insertTemplate = sql::model(sql, "INSERT INTO `clients` (`serverId`, `cldbId`, `clientUid`, `lastName`,`firstConnect`,`lastConnect`, `connections`) VALUES (:serverId, :cldbid, :cluid, :name, :fconnect, :lconnect, :connections)", variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()}, variable{":fconnect", duration_cast(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0}, variable{":connections", 0}); if(cldbid == 0){ //Completly new user res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([](ClientDbId* ptr, int length, char** values, char** names){ *ptr = static_cast(stoll(values[0])); return 0; }, &cldbid); pf(res); if(!res) return false; cldbid += 1; res = insertTemplate.command().values(variable{":serverId", 0}, variable{":cldbid", cldbid}).execute(); //Insert global pf(res); if(!res) return false; debugMessage(id, "Having new instance user on server {}. (Database ID: {} Name: {})", id, cldbid, cl->getDisplayName()); } else { debugMessage(id, "Having new server user on server {}. (Database ID: {} Name: {})", id, cldbid, cl->getDisplayName()); } if(id != 0){ //Else already inserted res = insertTemplate.command().values(variable{":serverId", id}, variable{":cldbid", cldbid}).execute(); pf(res); if(!res) return false; } return assignDatabaseId(sql, id, cl); } logTrace(id, "Loaded client from database. Database id: {} Unique id: {}", cl->getClientDatabaseId(), cl->getUid()); return true; } inline sql::result load_properties(ServerId sid, deque>& properties, sql::command& command) { auto start = system_clock::now(); auto result = command.query([&](int length, string* values, string* names) { string key, value; property::PropertyType type = property::PROP_TYPE_UNKNOWN; for(int index = 0; index < length; index++) { try { if(names[index] == "key") key = values[index]; else if(names[index] == "value") value = values[index]; else if(names[index] == "type") type = (property::PropertyType) stoll(values[index]); } catch(const std::exception& ex) { logError(sid, "Failed to load parse property \"{}\". key: {}, value: {}, message: {}", key,names[index],values[index],ex.what()); return 0; } } const auto &info = property::impl::info_key(type, key); if(info->name == "undefined") { logError(sid, "Found unknown property in database! ({})", key); return 0; } /* auto prop = properties->operator[](info); prop = value; prop.setModified(true); prop.setDbReference(true); */ auto data = make_unique(); data->value = value; data->type = info; properties.push_back(move(data)); return 0; }); auto end = system_clock::now(); auto time = end - start; logTrace(sid, "[SQL] load_properties(\"{}\") needs {}ms", command.sqlCommand(), duration_cast(time).count()); return result; } std::shared_ptr DatabaseHelper::loadServerProperties(const std::shared_ptr& server) { auto props = std::make_shared(); props->register_property_type(); (*props)[property::VIRTUALSERVER_HOST] = config::binding::DefaultVoiceHost; (*props)[property::VIRTUALSERVER_WEB_HOST] = config::binding::DefaultWebHost; bool loaded = false; if(use_startup_cache && server) { shared_ptr entry; { threads::MutexLock lock(this->startup_lock); for(const auto& entries : this->startup_entries) { if(entries->sid == server->getServerId()) { entry = entries; break; } } } if(entry) { for(const auto& prop : entry->properties) { if(prop->type == property::PROP_TYPE_SERVER && prop->id == 0) { auto p = (*props)[prop->info]; p = prop->value; p.setModified(true); p.setDbReference(true); } } loaded = true; } } if(!loaded) { auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", property::PropertyType::PROP_TYPE_SERVER}); deque> property_list; LOG_SQL_CMD(load_properties(server ? server->getServerId() : 0, property_list, command)); for(const auto& entry : property_list) { auto prop = props->operator[](entry->type); prop = entry->value; prop.setModified(true); prop.setDbReference(true); } } weak_ptr weak = server; ServerId serverId = server ? server->getServerId() : 0; props->registerNotifyHandler([&, serverId, weak](Property& prop){ if((prop.type().flags & property::FLAG_SAVE) == 0) { prop.setModified(false); return; } auto weak_server = weak.lock(); if(!weak_server && serverId != 0) return; string sql; if(prop.hasDbReference()) sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key"; else { prop.setDbReference(true); sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)"; } logTrace(serverId, "Updating server property: " + prop.type().name + ". New value: " + prop.value() + ". Query: " + sql); sql::command(this->sql, sql, variable{":sid", serverId}, variable{":type", property::PropertyType::PROP_TYPE_SERVER}, variable{":id", 0}, variable{":key", prop.type().name}, variable{":value", prop.value()} ).executeLater().waitAndGetLater(LOG_SQL_CMD, sql::result{1, "future failed"}); }); return props; } std::shared_ptr DatabaseHelper::loadPlaylistProperties(const std::shared_ptr& server, PlaylistId id) { auto props = std::make_shared(); props->register_property_type(); (*props)[property::PLAYLIST_ID] = id; bool loaded = false; if(use_startup_cache && server) { shared_ptr entry; { threads::MutexLock lock(this->startup_lock); for(const auto& entries : this->startup_entries) { if(entries->sid == server->getServerId()) { entry = entries; break; } } } if(entry) { for(const auto& prop : entry->properties) { if(prop->type == property::PROP_TYPE_PLAYLIST && prop->id == id) { auto p = (*props)[prop->info]; p = prop->value; p.setModified(true); p.setDbReference(true); } } loaded = true; } } if(!loaded) { auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", property::PropertyType::PROP_TYPE_PLAYLIST}, variable{":id", id}); deque> property_list; LOG_SQL_CMD(load_properties(server ? server->getServerId() : 0, property_list, command)); for(const auto& entry : property_list) { auto prop = props->operator[](entry->type); prop = entry->value; prop.setModified(true); prop.setDbReference(true); } } weak_ptr weak = server; ServerId serverId = server ? server->getServerId() : 0; props->registerNotifyHandler([&, serverId, weak, id](Property& prop){ if((prop.type().flags & property::FLAG_SAVE) == 0) { prop.setModified(false); return; } auto weak_server = weak.lock(); if(!weak_server && serverId != 0) return; string sql; if(prop.hasDbReference()) sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key"; else { prop.setDbReference(true); sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)"; } logTrace(serverId, "Updating playlist property for {}. Key: {} Value: {}", id, prop.type().name, prop.value()); sql::command(this->sql, sql, variable{":sid", serverId}, variable{":type", property::PropertyType::PROP_TYPE_PLAYLIST}, variable{":id", id}, variable{":key", prop.type().name}, variable{":value", prop.value()} ).executeLater().waitAndGetLater(LOG_SQL_CMD, sql::result{1, "future failed"}); }); return props; } std::shared_ptr DatabaseHelper::loadChannelProperties(const shared_ptr& server, ChannelId channel) { ServerId serverId = server ? server->getServerId() : 0U; auto props = std::make_shared(); props->register_property_type(); if(server) { props->operator[](property::CHANNEL_TOPIC) = server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_TOPIC].value(); props->operator[](property::CHANNEL_DESCRIPTION) = server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_DESCRIPTION].value(); } bool loaded = false; if(use_startup_cache && server) { shared_ptr entry; { threads::MutexLock lock(this->startup_lock); for(const auto& entries : this->startup_entries) { if(entries->sid == server->getServerId()) { entry = entries; break; } } } if(entry) { for(const auto& prop : entry->properties) { if(prop->type == property::PROP_TYPE_CHANNEL && prop->id == channel) { auto p = (*props)[prop->info]; p = prop->value; p.setModified(true); p.setDbReference(true); } } loaded = true; } } if(!loaded) { auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", serverId}, variable{":type", property::PropertyType::PROP_TYPE_CHANNEL}, variable{":id", channel}); deque> property_list; LOG_SQL_CMD(load_properties(serverId, property_list, command)); for(const auto& entry : property_list) { auto prop = props->operator[](entry->type); prop = entry->value; prop.setModified(true); prop.setDbReference(true); } } weak_ptr weak = server; props->registerNotifyHandler([&, weak, serverId, channel](Property& prop){ auto weak_server = weak.lock(); if(!weak_server && serverId != 0) return; if((prop.type().flags & property::FLAG_SAVE) == 0) return; if(!prop.isModified()) return; std::string query; if(prop.type() == property::CHANNEL_PID){ query = "UPDATE `channels` SET `parentId` = :value WHERE `serverId` = :serverId AND `channelId` = :id"; } else if(!prop.hasDbReference()){ query = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)"; } else { query = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `id` = :id AND `key` = :key AND `type` = :type"; } logTrace(serverId, "[CHANNEL] Updating channel property for channel {}: {}. New value: '{}'. Query: {}", channel, prop.type().name, prop.value(), query); sql::command(this->sql, query, variable{":serverId", serverId}, variable{":id", channel}, variable{":type", property::PropertyType::PROP_TYPE_CHANNEL}, variable{":key", prop.type().name}, variable{":value", prop.value()} ).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future error"}); prop.setModified(false); prop.setDbReference(true); }); return props; } std::shared_ptr DatabaseHelper::loadClientProperties(const std::shared_ptr& server, ClientDbId cldbid, ClientType type) { auto props = std::make_shared(); assign_default_properties_client(props.get(), type); if(server) { props->operator[](property::CLIENT_DESCRIPTION) = server->properties()[property::VIRTUALSERVER_DEFAULT_CLIENT_DESCRIPTION].value(); } bool loaded = false; if(use_startup_cache && server) { shared_ptr entry; { threads::MutexLock lock(this->startup_lock); for(const auto& entries : this->startup_entries) { if(entries->sid == server->getServerId()) { entry = entries; break; } } } if(entry) { for(const auto& prop : entry->properties) { if(prop->id == cldbid && (prop->type == property::PROP_TYPE_CLIENT || prop->type == property::PROP_TYPE_CONNECTION)) { auto p = (*props)[prop->info]; p = prop->value; p.setModified(true); p.setDbReference(true); } } loaded = true; } } if(!loaded) { auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type1", property::PropertyType::PROP_TYPE_CONNECTION}, variable{":type2", property::PropertyType::PROP_TYPE_CLIENT}, variable{":id", cldbid}); deque> property_list; LOG_SQL_CMD(load_properties(server ? server->getServerId() : 0, property_list, command)); for(const auto& entry : property_list) { auto prop = props->operator[](entry->type); prop = entry->value; prop.setModified(true); prop.setDbReference(true); } } weak_ptr weak_server = server; auto server_id = server ? server->getServerId() : 0; props->registerNotifyHandler([&, weak_server, server_id, cldbid](Property& prop){ //General save auto server = weak_server.lock(); if(!server && server_id != 0) { logError(server_id, "Tried to update client permissions of a expired server!"); return; } if(!prop.isModified()) return; if((prop.type().flags & property::FLAG_SAVE) == 0 && (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0) { logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + prop.type().name + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")"); return; } if(!prop.get_handle()) return; if(!prop.get_handle()->isSaveEnabled()) return; if(!prop.hasDbReference() && (prop.default_value() == prop.value())) return; //No changes to default value prop.setModified(false); std::string sql; if(prop.hasDbReference()) sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key"; else { prop.setDbReference(true); sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)"; } logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + prop.type().name + " value: " + prop.value()); sql::command(this->sql, sql, variable{":serverId", server ? server->getServerId() : 0}, variable{":type", prop.type().type_property}, variable{":id", cldbid}, variable{":key", prop.type().name}, variable{":value", prop.value()} ).executeLater().waitAndGetLater(LOG_SQL_CMD, sql::result{1, "future failed"}); }); props->registerNotifyHandler([&, weak_server, server_id, cldbid](Property& prop){ auto server = weak_server.lock(); if(!server && server_id != 0) { logError(server_id, "Tried to update client permissions of a expired server!"); return; } std::string query; if(prop.type() == property::CLIENT_TOTALCONNECTIONS) query = "UPDATE `clients` SET `connections` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid"; else if(prop.type() == property::CLIENT_NICKNAME) query = "UPDATE `clients` SET `lastName` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid"; else if(prop.type() == property::CLIENT_LASTCONNECTED) query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid"; if(query.empty()) return; debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '" + prop.type().name + "' for " + to_string(cldbid) + " (New value: " + prop.value() + ", SQL: " + query + ")"); sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":cldbid", cldbid}, variable{":value", prop.value()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"}); }); return props; } void DatabaseHelper::loadStartupCache() { this->loadStartupPermissionCache(); this->loadStartupPropertyCache(); this->use_startup_cache = true; } size_t DatabaseHelper::cacheBinarySize() { size_t result = 0; result += sizeof(this->startup_entries); for(const auto& entry : this->startup_entries) { result += sizeof(entry); result += sizeof(*entry.get()); for(const auto& e : entry->permissions) { result += sizeof(e); result += sizeof(e.get()); } for(const auto& e : entry->properties) { result += sizeof(e); result += sizeof(e.get()); result += e->value.length(); } } return result; } void DatabaseHelper::clearStartupCache(ts::ServerId sid) { if(sid == 0) { threads::MutexLock lock(this->startup_lock); this->startup_entries.clear(); this->use_startup_cache = false; } else { threads::MutexLock lock(this->startup_lock); /* this->startup_entries.erase(std::remove_if(this->startup_entries.begin(), this->startup_entries.end(), [&](const shared_ptr& entry) { return entry->sid == sid; }), this->startup_entries.end()); */ } } //SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId` //SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId` struct StartupPermissionArgument { std::shared_ptr current_server; }; void DatabaseHelper::loadStartupPermissionCache() { StartupPermissionArgument arg; sql::command(this->sql, "SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`").query([&](StartupPermissionArgument* arg, int length, char** values, char** names) { auto key = permission::PermissionTypeEntry::unknown; permission::PermissionValue value = 0, granted = 0; permission::PermissionSqlType type = SQL_PERM_GROUP; bool negated = false, skipped = false; ChannelId channel = 0; uint64_t id = 0; ServerId serverId = 0; int index; try { for(index = 0; index < length; index++) { if(strcmp(names[index], "permId") == 0) { key = permission::resolvePermissionData(values[index]); if(key->type == permission::unknown || key->type == permission::undefined) { debugMessage(0, "[SQL] Permission entry contains invalid permission type! Type: {}", values[index]); return 0; } } else if(strcmp(names[index], "channelId") == 0) { channel = stoull(values[index]); } else if(strcmp(names[index], "id") == 0) { id = stoull(values[index]); } else if(strcmp(names[index], "value") == 0) { value = stoi(values[index]); } else if(strcmp(names[index], "grant") == 0) { granted = stoi(values[index]); } else if(strcmp(names[index], "flag_skip") == 0) skipped = strcmp(values[index], "1") == 0; else if(strcmp(names[index], "flag_negate") == 0) negated = strcmp(values[index], "1") == 0; else if(strcmp(names[index], "serverId") == 0) serverId = stoll(values[index]); else if(strcmp(names[index], "type") == 0) type = static_cast(stoll(values[index])); } } catch(std::exception& ex) { logError(0, "[SQL] Cant load permissions! Failed to parse value! Message: {}. Key: {}, Value: \"{}\"", ex.what(),names[index],values[index]); return 0; } if(key == permission::PermissionTypeEntry::unknown) { debugMessage(0, "[SQL] Permission entry misses permission type!"); return 0; } if(serverId == 0) return 0; if(!arg->current_server || arg->current_server->sid != serverId) { arg->current_server = nullptr; { threads::MutexLock lock(this->startup_lock); for(const auto& entry : this->startup_entries) { if(entry->sid == serverId) { arg->current_server = entry; break; } } if(!arg->current_server) { arg->current_server = make_shared(); arg->current_server->sid = serverId; this->startup_entries.push_back(arg->current_server); } } } auto entry = make_unique(); entry->permission = key; entry->type = type; entry->value = value; entry->grant = granted; entry->flag_negate = negated; entry->flag_skip = skipped; entry->id = id; entry->channelId = channel; arg->current_server->permissions.push_back(std::move(entry)); return 0; }, &arg); } void DatabaseHelper::loadStartupPropertyCache() { StartupPermissionArgument arg; sql::command(this->sql, "SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`").query([&](StartupPermissionArgument* arg, int length, char** values, char** names) { std::string key, value; property::PropertyType type = property::PROP_TYPE_UNKNOWN; ServerId serverId = 0; uint64_t id = 0; for(int index = 0; index < length; index++) { try { if(strcmp(names[index], "key") == 0) key = values[index]; else if(strcmp(names[index], "value") == 0) value = values[index] == nullptr ? "" : values[index]; else if(strcmp(names[index], "type") == 0) type = (property::PropertyType) stoll(values[index]); else if(strcmp(names[index], "serverId") == 0) serverId = stoll(values[index]); else if(strcmp(names[index], "id") == 0) id = stoll(values[index]); } catch(const std::exception& ex) { logError(0, "[SQL] Cant load property! Failed to parse value! Message: {}. Key: {}, Value: \"{}\"", ex.what(),names[index],values[index]); return 0; } } auto info = property::impl::info_key(type, key); if(info == property::PropertyDescription::unknown) { logError(serverId, "Invalid property ({} | {})", key, type); return 0; } if(serverId == 0) return 0; if(!arg->current_server || arg->current_server->sid != serverId) { arg->current_server = nullptr; { threads::MutexLock lock(this->startup_lock); for(const auto& entry : this->startup_entries) { if(entry->sid == serverId) { arg->current_server = entry; break; } } if(!arg->current_server) { arg->current_server = make_shared(); arg->current_server->sid = serverId; this->startup_entries.push_back(arg->current_server); } } } auto entry = make_unique(); entry->info = info; entry->value = value; entry->id = id; entry->type = type; arg->current_server->properties.push_back(std::move(entry)); return 0; }, &arg); } bool DatabaseHelper::deleteGroupPermissions(const std::shared_ptr &server, ts::GroupId group_id) { auto command = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", permission::SQL_PERM_GROUP}, variable{":id", group_id}).execute(); LOG_SQL_CMD(command); return !!command; } bool DatabaseHelper::deleteChannelPermissions(const std::shared_ptr &server, ts::ChannelId channel_id) { auto command = sql::command(sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `channelId` = :chid", variable{":serverId", server ? server->getServerId() : 0}, variable{":chid", channel_id}).execute(); LOG_SQL_CMD(command); return !!command; } std::deque> DatabaseHelper::query_properties(ts::ServerId server_id, ts::property::PropertyType type, uint64_t id) { deque> result; auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server_id}, variable{":type", type}, variable{":id", id}); LOG_SQL_CMD(load_properties(server_id, result, command)); return result; } bool DatabaseHelper::deletePlaylist(const std::shared_ptr &server, ts::PlaylistId playlist_id) { auto server_id = server ? server->getServerId() : (ServerId) 0; sql::command(this->sql, "DELETE FROM `playlists` WHERE `serverId` = :server_id AND `playlist_id` = :playlist_id", variable{":server_id", server_id}, variable{":playlist_id", playlist_id} ).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete playlist " + to_string(playlist_id) + " from table `playlists`"}); sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", permission::SQL_PERM_PLAYLIST}, variable{":id", playlist_id} ).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete playlist permissions for playlist " + to_string(playlist_id)}); sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", property::PROP_TYPE_PLAYLIST}, variable{":id", playlist_id} ).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete playlist properties for playlist " + to_string(playlist_id)}); return true; }