#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(LOG_GENERAL, "Invalid db key for manager data. Key: {}", 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(server ? server->getServerId() : 0, "[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(server ? server->getServerId() : 0, "[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 } inline sql::result load_permissions_v2( const std::shared_ptr& server, v2::PermissionManager* manager, sql::command& command, bool test_channel, /* only used for client permissions (client channel permissions) */ bool is_channel) { 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 = permNotGranted, granted = permNotGranted; bool negated = false, skipped = false; ChannelId channel_id{0}; 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) { channel_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; } } 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(channel_id > 0 && test_channel) { if(!server) logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]); else { auto channel = server->getChannelTree()->findChannel(channel_id); if(!channel) logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]); } } if(key == permission::undefined) { debugMessage(server_id, "[SQL] Permission entry misses permission type! Command: {}", command.sqlCommand()); return 0; } if(channel_id == 0 || is_channel) manager->load_permission(key, {value, granted}, skipped, negated, value != permNotGranted, granted != permNotGranted); else manager->load_permission(key, {value, granted}, channel_id, skipped, negated, 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 permission_manager = 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) permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, channel->channelId(), perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted); else permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_skip, perm->flag_negate, 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, permission_manager.get(), command, true, false)); } #ifndef DISABLE_CACHING this->permManagerLock.lock(); auto entry = new CachedPermissionManager(); entry->sid = server_id; entry->manager = permission_manager; entry->ownLock = permission_manager; entry->cldbid = cldbid; entry->lastAccess = system_clock::now(); this->cachedPermissionManagers.push_back(entry); this->permManagerLock.unlock(); #endif return permission_manager; } 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); auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value; auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant; 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", update.channel_id}, variable{":type", permission::SQL_PERM_USER}, variable{":permId", permission_data->name}, variable{":value", value}, variable{":grant", 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_skip, perm->flag_negate, 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, 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); auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value; auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant; logTrace(server_id, "[CHANNEL] Updating group permission for group {}: {}. New value: {}. New grant: {}. Query: {}", group_id, permission_data->name, value, 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", value}, variable{":grant", 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) { if(perm->channelId) result->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->channelId, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted); else result->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted); } } } return 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_v2(server, result.get(), command, false, false)); return result; } void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr &server, PlaylistId pid, 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); auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value; auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant; logTrace(server_id, "[PLAYLIST] Updating playlist permission for playlist {}: {}. New value: {}. New grant: {}. Query: {}", pid, permission_data->name, value, grant, query ); sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":id", pid}, variable{":chId", update.channel_id}, variable{":type", permission::SQL_PERM_PLAYLIST}, variable{":permId", permission_data->name}, variable{":value", value}, variable{":grant", 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::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_skip, perm->flag_negate, 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, true)); 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 value = update.update_value == v2::delete_value ? permNotGranted : update.values.value; auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant; 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, value, 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", value}, variable{":grant", 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::default_properties_client(std::shared_ptr properties, ClientType type){ if(!properties) properties = make_shared(); properties->register_property_type(); properties->register_property_type(); if(type == ClientType::CLIENT_MUSIC || type == ClientType::CLIENT_QUERY){ (*properties)[property::CLIENT_INPUT_HARDWARE] = true; (*properties)[property::CLIENT_OUTPUT_HARDWARE] = true; } return properties; } std::mutex DatabaseHelper::database_id_mutex{}; bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::shared_ptr cl) { cl->loadDataForCurrentServer(); if(cl->getClientDatabaseId() == 0){ //Client does not exist ClientDbId new_client_database_id{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; }, &new_client_database_id); 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(new_client_database_id == 0) { /* we've a completely new user */ std::lock_guard db_id_lock{DatabaseHelper::database_id_mutex}; res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([&](int length, std::string* values, std::string* names) { assert(length == 1); new_client_database_id = (ClientDbId) stoll(values[0]); }); LOG_SQL_CMD(res); if(!res) return false; new_client_database_id += 1; res = insertTemplate.command().values(variable{":serverId", 0}, variable{":cldbid", new_client_database_id}).execute(); //Insert global LOG_SQL_CMD(res); if(!res) return false; debugMessage(LOG_INSTANCE, "Registered a new client. Unique id: {}, First server: {}, Database ID: {}", cl->getUid(), id, new_client_database_id); } else { debugMessage(id, "Having new client, which is already known on this instance. Unique id: {}, First server: {}, Database ID: {}", cl->getUid(), id, new_client_database_id); } if(id != 0){ //Else already inserted res = insertTemplate.command().values(variable{":serverId", id}, variable{":cldbid", new_client_database_id}).execute(); pf(res); if(!res) return false; } return assignDatabaseId(sql, id, cl); } logTrace(id, "Loaded client successfully 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::find(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 = std::make_unique(); data->type = &info; data->value = value; 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: " + std::string{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 = DatabaseHelper::default_properties_client(nullptr, 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, type](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 && (type != ClientType::CLIENT_MUSIC || (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0)) { logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + std::string{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: " + std::string{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 '" + std::string{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 = permNotGranted, granted = permNotGranted; 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; } } const auto& info = property::find(type, key); if(info.is_undefined()) { 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; }