X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;ds=sidebyside;f=src%2Fserver.cpp;h=013c039c3c13ef1ac342240cdb5e37c05b7942e5;hb=261a8db9dd0403f8d0a7d71f46d4cb272e217cd1;hp=aba7b64010c37bb64089534b130a9a6365cd9a3a;hpb=37a05ec8d6cbf9ff4432225cffe78c16fdd0647d;p=dragonfireclient.git diff --git a/src/server.cpp b/src/server.cpp index aba7b6401..013c039c3 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -66,6 +66,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server/player_sao.h" #include "server/serverinventorymgr.h" #include "translation.h" +#include "database/database-sqlite3.h" +#include "database/database-files.h" +#include "database/database-dummy.h" +#include "gameparams.h" class ClientNotFoundException : public BaseException { @@ -103,7 +107,13 @@ void *ServerThread::run() * doesn't busy wait) and will process any remaining packets. */ - m_server->AsyncRunStep(true); + try { + m_server->AsyncRunStep(true); + } catch (con::ConnectionBindFailed &e) { + m_server->setAsyncFatalError(e.what()); + } catch (LuaError &e) { + m_server->setAsyncFatalError(e); + } while (!stopRequested()) { try { @@ -117,8 +127,7 @@ void *ServerThread::run() } catch (con::ConnectionBindFailed &e) { m_server->setAsyncFatalError(e.what()); } catch (LuaError &e) { - m_server->setAsyncFatalError( - "ServerThread::run Lua: " + std::string(e.what())); + m_server->setAsyncFatalError(e); } } @@ -245,7 +254,7 @@ Server::Server( #if USE_PROMETHEUS m_metrics_backend = std::unique_ptr(createPrometheusMetricsBackend()); #else - m_metrics_backend = std::unique_ptr(new MetricsBackend()); + m_metrics_backend = std::make_unique(); #endif m_uptime_counter = m_metrics_backend->addCounter("minetest_core_server_uptime", "Server uptime (in seconds)"); @@ -259,9 +268,15 @@ Server::Server( "minetest_core_latency", "Latency value (in seconds)"); - m_aom_buffer_counter = m_metrics_backend->addCounter( - "minetest_core_aom_generated_count", - "Number of active object messages generated"); + + const std::string aom_types[] = {"reliable", "unreliable"}; + for (u32 i = 0; i < ARRLEN(aom_types); i++) { + std::string help_str("Number of active object messages generated ("); + help_str.append(aom_types[i]).append(")"); + m_aom_buffer_counter[i] = m_metrics_backend->addCounter( + "minetest_core_aom_generated_count", help_str, + {{"type", aom_types[i]}}); + } m_packet_recv_counter = m_metrics_backend->addCounter( "minetest_core_server_packet_recv", @@ -271,6 +286,10 @@ Server::Server( "minetest_core_server_packet_recv_processed", "Valid received packets processed"); + m_map_edit_event_counter = m_metrics_backend->addCounter( + "minetest_core_map_edit_events", + "Number of map edit events"); + m_lag_gauge->set(g_settings->getFloat("dedicated_server_step")); } @@ -339,10 +358,15 @@ Server::~Server() delete m_thread; } + // Write any changes before deletion. + if (m_mod_storage_database) + m_mod_storage_database->endSave(); + // Delete things in the reverse order of creation delete m_emerge; delete m_env; delete m_rollback; + delete m_mod_storage_database; delete m_banmanager; delete m_itemdef; delete m_nodedef; @@ -351,6 +375,7 @@ Server::~Server() // Deinitialize scripting infostream << "Server: Deinitializing scripting" << std::endl; delete m_script; + delete m_startup_server_map; // if available delete m_game_settings; while (!m_unsent_map_edit_queue.empty()) { @@ -381,13 +406,17 @@ void Server::init() } // Create emerge manager - m_emerge = new EmergeManager(this); + m_emerge = new EmergeManager(this, m_metrics_backend.get()); // Create ban manager std::string ban_path = m_path_world + DIR_DELIM "ipban.txt"; m_banmanager = new BanManager(ban_path); - m_modmgr = std::unique_ptr(new ServerModManager(m_path_world)); + // Create mod storage database and begin a save for later + m_mod_storage_database = openModStorageDatabase(m_path_world); + m_mod_storage_database->beginSave(); + + m_modmgr = std::make_unique(m_path_world); std::vector unsatisfied_mods = m_modmgr->getUnsatisfiedMods(); // complain about mods with unsatisfied dependencies if (!m_modmgr->isConsistent()) { @@ -399,6 +428,7 @@ void Server::init() // Create the Map (loads map_meta.txt, overriding configured mapgen params) ServerMap *servermap = new ServerMap(m_path_world, this, m_emerge, m_metrics_backend.get()); + m_startup_server_map = servermap; // Initialize scripting infostream << "Server: Initializing Lua" << std::endl; @@ -406,10 +436,11 @@ void Server::init() m_script = new ServerScripting(this); // Must be created before mod loading because we have some inventory creation - m_inventory_mgr = std::unique_ptr(new ServerInventoryManager()); + m_inventory_mgr = std::make_unique(); m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME); + m_gamespec.checkAndLog(); m_modmgr->loadMods(m_script); // Read Textures and calculate sha1 sums @@ -440,7 +471,9 @@ void Server::init() m_craftdef->initHashes(this); // Initialize Environment - m_env = new ServerEnvironment(servermap, m_script, this, m_path_world); + m_startup_server_map = nullptr; // Ownership moved to ServerEnvironment + m_env = new ServerEnvironment(servermap, m_script, this, + m_path_world, m_metrics_backend.get()); m_inventory_mgr->setEnv(m_env); m_clients.setEnv(m_env); @@ -459,6 +492,9 @@ void Server::init() // Give environment reference to scripting api m_script->initializeEnvironment(m_env); + // Do this after regular script init is done + m_script->initAsync(); + // Register us to receive map edit events servermap->addEventReceiver(this); @@ -491,16 +527,17 @@ void Server::start() // ASCII art for the win! std::cerr - << " .__ __ __ " << std::endl - << " _____ |__| ____ _____/ |_ ____ _______/ |_ " << std::endl - << " / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\" << std::endl - << "| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | " << std::endl - << "|__|_| /__|___| /\\___ >__| \\___ >____ > |__| " << std::endl - << " \\/ \\/ \\/ \\/ \\/ " << std::endl; + << " __. __. __. " << std::endl + << " _____ |__| ____ _____ / |_ _____ _____ / |_ " << std::endl + << " / \\| |/ \\ / __ \\ _\\/ __ \\/ __> _\\" << std::endl + << "| Y Y \\ | | \\ ___/| | | ___/\\___ \\| | " << std::endl + << "|__|_| / |___| /\\______> | \\______>_____/| | " << std::endl + << " \\/ \\/ \\/ \\/ \\/ " << std::endl; actionstream << "World at [" << m_path_world << "]" << std::endl; actionstream << "Server for gameid=\"" << m_gamespec.id - << "\" listening on " << m_bind_addr.serializeString() << ":" - << m_bind_addr.getPort() << "." << std::endl; + << "\" listening on "; + m_bind_addr.print(actionstream); + actionstream << "." << std::endl; } void Server::stop() @@ -509,9 +546,7 @@ void Server::stop() // Stop threads (set run=false first so both start stopping) m_thread->stop(); - //m_emergethread.setRun(false); m_thread->wait(); - //m_emergethread.stop(); infostream<<"Server: Threads stopped"<reportMaxLagEstimate(max_lag); + // Step environment m_env->step(dtime); } @@ -645,7 +681,7 @@ void Server::AsyncRunStep(bool initial_step) ScopeProfiler sp(g_profiler, "Server: liquid transform"); std::map modified_blocks; - m_env->getMap().transformLiquids(modified_blocks, m_env); + m_env->getServerMap().transformLiquids(modified_blocks, m_env); /* Set the modified blocks unsent for all the clients @@ -662,6 +698,17 @@ void Server::AsyncRunStep(bool initial_step) } else { m_lag_gauge->increment(dtime/100); } + + { + float &counter = m_step_pending_dyn_media_timer; + counter += dtime; + if (counter >= 5.0f) { + stepPendingDynMediaCallbacks(counter); + counter = 0; + } + } + + #if USE_CURL // send masterserver announce { @@ -692,43 +739,36 @@ void Server::AsyncRunStep(bool initial_step) //infostream<<"Server: Checking added and deleted active objects"<set(clients.size()); - for (const auto &client_it : clients) { - RemoteClient *client = client_it.second; + m_player_gauge->set(clients.size()); + for (const auto &client_it : clients) { + RemoteClient *client = client_it.second; - if (client->getState() < CS_DefinitionsSent) - continue; + if (client->getState() < CS_DefinitionsSent) + continue; - // This can happen if the client times out somehow - if (!m_env->getPlayer(client->peer_id)) - continue; + // This can happen if the client times out somehow + if (!m_env->getPlayer(client->peer_id)) + continue; - PlayerSAO *playersao = getPlayerSAO(client->peer_id); - if (!playersao) - continue; + PlayerSAO *playersao = getPlayerSAO(client->peer_id); + if (!playersao) + continue; - SendActiveObjectRemoveAdd(client, playersao); + SendActiveObjectRemoveAdd(client, playersao); + } } - m_clients.unlock(); - // Save mod storages if modified + // Write changes to the mod storage m_mod_storage_save_timer -= dtime; if (m_mod_storage_save_timer <= 0.0f) { m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); - int n = 0; - for (std::unordered_map::const_iterator - it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { - if (it->second->isModified()) { - it->second->save(getModStoragePath()); - n++; - } - } - if (n > 0) - infostream << "Saved " << n << " modified mod storages." << std::endl; + m_mod_storage_database->endSave(); + m_mod_storage_database->beginSave(); } } @@ -745,10 +785,14 @@ void Server::AsyncRunStep(bool initial_step) // Get active object messages from environment ActiveObjectMessage aom(0); - u32 aom_count = 0; + u32 count_reliable = 0, count_unreliable = 0; for(;;) { if (!m_env->getActiveObjectMessage(&aom)) break; + if (aom.reliable) + count_reliable++; + else + count_unreliable++; std::vector* message_list = nullptr; auto n = buffered_messages.find(aom.id); @@ -759,68 +803,69 @@ void Server::AsyncRunStep(bool initial_step) message_list = n->second; } message_list->push_back(std::move(aom)); - aom_count++; - } - - m_aom_buffer_counter->increment(aom_count); - - m_clients.lock(); - const RemoteClientMap &clients = m_clients.getClientList(); - // Route data to every client - std::string reliable_data, unreliable_data; - for (const auto &client_it : clients) { - reliable_data.clear(); - unreliable_data.clear(); - RemoteClient *client = client_it.second; - PlayerSAO *player = getPlayerSAO(client->peer_id); - // Go through all objects in message buffer - for (const auto &buffered_message : buffered_messages) { - // If object does not exist or is not known by client, skip it - u16 id = buffered_message.first; - ServerActiveObject *sao = m_env->getActiveObject(id); - if (!sao || client->m_known_objects.find(id) == client->m_known_objects.end()) - continue; + } - // Get message list of object - std::vector* list = buffered_message.second; - // Go through every message - for (const ActiveObjectMessage &aom : *list) { - // Send position updates to players who do not see the attachment - if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) { - if (sao->getId() == player->getId()) - continue; - - // Do not send position updates for attached players - // as long the parent is known to the client - ServerActiveObject *parent = sao->getParent(); - if (parent && client->m_known_objects.find(parent->getId()) != - client->m_known_objects.end()) - continue; - } + m_aom_buffer_counter[0]->increment(count_reliable); + m_aom_buffer_counter[1]->increment(count_unreliable); + + { + ClientInterface::AutoLock clientlock(m_clients); + const RemoteClientMap &clients = m_clients.getClientList(); + // Route data to every client + std::string reliable_data, unreliable_data; + for (const auto &client_it : clients) { + reliable_data.clear(); + unreliable_data.clear(); + RemoteClient *client = client_it.second; + PlayerSAO *player = getPlayerSAO(client->peer_id); + // Go through all objects in message buffer + for (const auto &buffered_message : buffered_messages) { + // If object does not exist or is not known by client, skip it + u16 id = buffered_message.first; + ServerActiveObject *sao = m_env->getActiveObject(id); + if (!sao || client->m_known_objects.find(id) == client->m_known_objects.end()) + continue; - // Add full new data to appropriate buffer - std::string &buffer = aom.reliable ? reliable_data : unreliable_data; - char idbuf[2]; - writeU16((u8*) idbuf, aom.id); - // u16 id - // std::string data - buffer.append(idbuf, sizeof(idbuf)); - buffer.append(serializeString16(aom.datastring)); + // Get message list of object + std::vector* list = buffered_message.second; + // Go through every message + for (const ActiveObjectMessage &aom : *list) { + // Send position updates to players who do not see the attachment + if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) { + if (sao->getId() == player->getId()) + continue; + + // Do not send position updates for attached players + // as long the parent is known to the client + ServerActiveObject *parent = sao->getParent(); + if (parent && client->m_known_objects.find(parent->getId()) != + client->m_known_objects.end()) + continue; + } + + // Add full new data to appropriate buffer + std::string &buffer = aom.reliable ? reliable_data : unreliable_data; + char idbuf[2]; + writeU16((u8*) idbuf, aom.id); + // u16 id + // std::string data + buffer.append(idbuf, sizeof(idbuf)); + buffer.append(serializeString16(aom.datastring)); + } + } + /* + reliable_data and unreliable_data are now ready. + Send them. + */ + if (!reliable_data.empty()) { + SendActiveObjectMessages(client->peer_id, reliable_data); } - } - /* - reliable_data and unreliable_data are now ready. - Send them. - */ - if (!reliable_data.empty()) { - SendActiveObjectMessages(client->peer_id, reliable_data); - } - if (!unreliable_data.empty()) { - SendActiveObjectMessages(client->peer_id, unreliable_data, false); + if (!unreliable_data.empty()) { + SendActiveObjectMessages(client->peer_id, unreliable_data, false); + } } } - m_clients.unlock(); // Clear buffered_messages for (auto &buffered_message : buffered_messages) { @@ -835,20 +880,18 @@ void Server::AsyncRunStep(bool initial_step) // We will be accessing the environment MutexAutoLock lock(m_env_mutex); - // Don't send too many at a time - //u32 count = 0; - - // Single change sending is disabled if queue size is not small + // Single change sending is disabled if queue size is big bool disable_single_change_sending = false; if(m_unsent_map_edit_queue.size() >= 4) disable_single_change_sending = true; - int event_count = m_unsent_map_edit_queue.size(); + const auto event_count = m_unsent_map_edit_queue.size(); + m_map_edit_event_counter->increment(event_count); // We'll log the amount of each Profiler prof; - std::list node_meta_updates; + std::unordered_set node_meta_updates; while (!m_unsent_map_edit_queue.empty()) { MapEditEvent* event = m_unsent_map_edit_queue.front(); @@ -875,9 +918,7 @@ void Server::AsyncRunStep(bool initial_step) case MEET_BLOCK_NODE_METADATA_CHANGED: { prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1); if (!event->is_private_change) { - // Don't send the change yet. Collect them to eliminate dupes. - node_meta_updates.remove(event->p); - node_meta_updates.push_back(event->p); + node_meta_updates.emplace(event->p); } if (MapBlock *block = m_env->getMap().getBlockNoCreateNoEx( @@ -930,19 +971,19 @@ void Server::AsyncRunStep(bool initial_step) } // Send all metadata updates - if (node_meta_updates.size()) + if (!node_meta_updates.empty()) sendMetadataChanged(node_meta_updates); } /* - Trigger emergethread (it somehow gets to a non-triggered but - bysy state sometimes) + Trigger emerge thread + Doing this every 2s is left over from old code, unclear if this is still needed. */ { float &counter = m_emergethread_trigger_timer; - counter += dtime; - if (counter >= 2.0) { - counter = 0.0; + counter -= dtime; + if (counter <= 0.0f) { + counter = 2.0f; m_emerge->startThreads(); } @@ -1013,8 +1054,7 @@ void Server::Receive() } catch (const ClientStateError &e) { errorstream << "ProcessData: peer=" << peer_id << " what()=" << e.what() << std::endl; - DenyAccess_Legacy(peer_id, L"Your client sent something server didn't expect." - L"Try reconnecting or updating your client"); + DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); } catch (const con::PeerNotFoundException &e) { // Do nothing } catch (const con::NoIncomingDataException &e) { @@ -1027,18 +1067,14 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) { std::string playername; PlayerSAO *playersao = NULL; - m_clients.lock(); - try { + { + ClientInterface::AutoLock clientlock(m_clients); RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_InitDone); if (client) { playername = client->getName(); playersao = emergePlayer(playername.c_str(), peer_id, client->net_proto_version); } - } catch (std::exception &e) { - m_clients.unlock(); - throw; } - m_clients.unlock(); RemotePlayer *player = m_env->getPlayer(playername.c_str()); @@ -1047,15 +1083,13 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) if (player && player->getPeerId() != PEER_ID_INEXISTENT) { actionstream << "Server: Failed to emerge player \"" << playername << "\" (player allocated to an another client)" << std::endl; - DenyAccess_Legacy(peer_id, L"Another client is connected with this " - L"name. If your client closed unexpectedly, try again in " - L"a minute."); + DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED); } else { errorstream << "Server: " << playername << ": Failed to emerge player" << std::endl; - DenyAccess_Legacy(peer_id, L"Could not allocate player."); + DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); } - return NULL; + return nullptr; } /* @@ -1072,31 +1106,32 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) // Send inventory SendInventory(playersao, false); - // Send HP or death screen + // Send HP + SendPlayerHP(playersao); + + // Send death screen if (playersao->isDead()) SendDeathscreen(peer_id, false, v3f(0,0,0)); - else - SendPlayerHPOrDie(playersao, - PlayerHPChangeReason(PlayerHPChangeReason::SET_HP)); // Send Breath SendPlayerBreath(playersao); /* - Print out action + Update player list and print action */ { - Address addr = getPeerAddress(player->getPeerId()); - std::string ip_str = addr.serializeString(); - const std::vector &names = m_clients.getPlayerNames(); + NetworkPacket notice_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, PEER_ID_INEXISTENT); + notice_pkt << (u8) PLAYER_LIST_ADD << (u16) 1 << std::string(player->getName()); + m_clients.sendToAll(¬ice_pkt); + } + { + std::string ip_str = getPeerAddress(player->getPeerId()).serializeString(); + const auto &names = m_clients.getPlayerNames(); actionstream << player->getName() << " [" << ip_str << "] joins game. List of players: "; - - for (const std::string &name : names) { + for (const std::string &name : names) actionstream << name << " "; - } - - actionstream << player->getName() <getName() << std::endl; } return playersao; } @@ -1119,18 +1154,16 @@ void Server::ProcessData(NetworkPacket *pkt) Address address = getPeerAddress(peer_id); std::string addr_s = address.serializeString(); - if(m_banmanager->isIpBanned(addr_s)) { + // FIXME: Isn't it a bit excessive to check this for every packet? + if (m_banmanager->isIpBanned(addr_s)) { std::string ban_name = m_banmanager->getBanName(addr_s); infostream << "Server: A banned client tried to connect from " - << addr_s << "; banned name was " - << ban_name << std::endl; - // This actually doesn't seem to transfer to the client - DenyAccess_Legacy(peer_id, L"Your ip is banned. Banned name was " - + utf8_to_wide(ban_name)); + << addr_s << "; banned name was " << ban_name << std::endl; + DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, + "Your IP is banned. Banned name was " + ban_name); return; } - } - catch(con::PeerNotFoundException &e) { + } catch (con::PeerNotFoundException &e) { /* * no peer for this packet found * most common reason is peer timeout, e.g. peer didn't @@ -1210,13 +1243,12 @@ void Server::onMapEditEvent(const MapEditEvent &event) void Server::SetBlocksNotSent(std::map& block) { std::vector clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); // Set the modified blocks unsent for all the clients for (const session_t client_id : clients) { if (RemoteClient *client = m_clients.lockedGetClientNoEx(client_id)) client->SetBlocksNotSent(block); } - m_clients.unlock(); } void Server::peerAdded(con::Peer *peer) @@ -1242,39 +1274,26 @@ bool Server::getClientConInfo(session_t peer_id, con::rtt_stat_type type, float* return *retval != -1; } -bool Server::getClientInfo( - session_t peer_id, - ClientState* state, - u32* uptime, - u8* ser_vers, - u16* prot_vers, - u8* major, - u8* minor, - u8* patch, - std::string* vers_string, - std::string* lang_code - ) -{ - *state = m_clients.getClientState(peer_id); - m_clients.lock(); +bool Server::getClientInfo(session_t peer_id, ClientInfo &ret) +{ + ClientInterface::AutoLock clientlock(m_clients); RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_Invalid); - if (!client) { - m_clients.unlock(); + if (!client) return false; - } - *uptime = client->uptime(); - *ser_vers = client->serialization_version; - *prot_vers = client->net_proto_version; + ret.state = client->getState(); + ret.addr = client->getAddress(); + ret.uptime = client->uptime(); + ret.ser_vers = client->serialization_version; + ret.prot_vers = client->net_proto_version; - *major = client->getMajor(); - *minor = client->getMinor(); - *patch = client->getPatch(); - *vers_string = client->getFull(); - *lang_code = client->getLangCode(); + ret.major = client->getMajor(); + ret.minor = client->getMinor(); + ret.patch = client->getPatch(); + ret.vers_string = client->getFullVer(); - m_clients.unlock(); + ret.lang_code = client->getLangCode(); return true; } @@ -1352,18 +1371,21 @@ void Server::SendMovement(session_t peer_id) Send(&pkt); } -void Server::SendPlayerHPOrDie(PlayerSAO *playersao, const PlayerHPChangeReason &reason) +void Server::HandlePlayerHPChange(PlayerSAO *playersao, const PlayerHPChangeReason &reason) { - if (playersao->isImmortal()) - return; + m_script->player_event(playersao, "health_changed"); + SendPlayerHP(playersao); - session_t peer_id = playersao->getPeerID(); - bool is_alive = playersao->getHP() > 0; + // Send to other clients + playersao->sendPunchCommand(); - if (is_alive) - SendPlayerHP(peer_id); - else - DiePlayer(peer_id, reason); + if (playersao->isDead()) + HandlePlayerDeath(playersao, reason); +} + +void Server::SendPlayerHP(PlayerSAO *playersao) +{ + SendHP(playersao->getPeerID(), playersao->getHP()); } void Server::SendHP(session_t peer_id, u16 hp) @@ -1395,13 +1417,6 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason, Send(&pkt); } -void Server::SendAccessDenied_Legacy(session_t peer_id,const std::wstring &reason) -{ - NetworkPacket pkt(TOCLIENT_ACCESS_DENIED_LEGACY, 0, peer_id); - pkt << reason; - Send(&pkt); -} - void Server::SendDeathscreen(session_t peer_id, bool set_camera_point_target, v3f camera_point_target) { @@ -1491,7 +1506,8 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message) NetworkPacket pkt(TOCLIENT_CHAT_MESSAGE, 0, peer_id); u8 version = 1; u8 type = message.type; - pkt << version << type << std::wstring(L"") << message.message << (u64)message.timestamp; + pkt << version << type << message.sender << message.message + << static_cast(message.timestamp); if (peer_id != PEER_ID_INEXISTENT) { RemotePlayer *player = m_env->getPlayer(peer_id); @@ -1643,7 +1659,7 @@ void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form) pkt << id << (u8) form->type << form->pos << form->name << form->scale << form->text << form->number << form->item << form->dir << form->align << form->offset << form->world_pos << form->size - << form->z_index << form->text2; + << form->z_index << form->text2 << form->style; Send(&pkt); } @@ -1678,10 +1694,7 @@ void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void case HUD_STAT_SIZE: pkt << *(v2s32 *) value; break; - case HUD_STAT_NUMBER: - case HUD_STAT_ITEM: - case HUD_STAT_DIR: - default: + default: // all other types pkt << *(u32 *) value; break; } @@ -1786,6 +1799,16 @@ void Server::SendOverrideDayNightRatio(session_t peer_id, bool do_override, Send(&pkt); } +void Server::SendSetLighting(session_t peer_id, const Lighting &lighting) +{ + NetworkPacket pkt(TOCLIENT_SET_LIGHTING, + 4, peer_id); + + pkt << lighting.shadow_intensity; + + Send(&pkt); +} + void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) { NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id); @@ -1799,18 +1822,6 @@ void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) } } -void Server::SendPlayerHP(session_t peer_id) -{ - PlayerSAO *playersao = getPlayerSAO(peer_id); - assert(playersao); - - SendHP(peer_id, playersao->getHP()); - m_script->player_event(playersao,"health_changed"); - - // Send to other clients - playersao->sendPunchCommand(); -} - void Server::SendPlayerBreath(PlayerSAO *sao) { assert(sao); @@ -1826,6 +1837,9 @@ void Server::SendMovePlayer(session_t peer_id) PlayerSAO *sao = player->getPlayerSAO(); assert(sao); + // Send attachment updates instantly to the client prior updating position + sao->sendOutdatedData(); + NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, peer_id); pkt << sao->getBasePosition() << sao->getLookPitch() << sao->getRotation().Y; @@ -2212,7 +2226,7 @@ void Server::sendRemoveNode(v3s16 p, std::unordered_set *far_players, pkt << p; std::vector clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); for (session_t client_id : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(client_id); @@ -2235,8 +2249,6 @@ void Server::sendRemoveNode(v3s16 p, std::unordered_set *far_players, // Send as reliable m_clients.send(client_id, 0, &pkt, true); } - - m_clients.unlock(); } void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set *far_players, @@ -2251,7 +2263,7 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set *far_player << (u8) (remove_metadata ? 0 : 1); std::vector clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); for (session_t client_id : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(client_id); @@ -2274,35 +2286,35 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set *far_player // Send as reliable m_clients.send(client_id, 0, &pkt, true); } - - m_clients.unlock(); } -void Server::sendMetadataChanged(const std::list &meta_updates, float far_d_nodes) +void Server::sendMetadataChanged(const std::unordered_set &positions, float far_d_nodes) { - float maxd = far_d_nodes * BS; NodeMetadataList meta_updates_list(false); - std::vector clients = m_clients.getClientIDs(); + std::ostringstream os(std::ios::binary); - m_clients.lock(); + std::vector clients = m_clients.getClientIDs(); + ClientInterface::AutoLock clientlock(m_clients); for (session_t i : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(i); if (!client) continue; - ServerActiveObject *player = m_env->getActiveObject(i); - v3f player_pos = player ? player->getBasePosition() : v3f(); + ServerActiveObject *player = getPlayerSAO(i); + v3s16 player_pos; + if (player) + player_pos = floatToInt(player->getBasePosition(), BS); - for (const v3s16 &pos : meta_updates) { + for (const v3s16 pos : positions) { NodeMetadata *meta = m_env->getMap().getNodeMetadata(pos); if (!meta) continue; v3s16 block_pos = getNodeBlockPos(pos); - if (!client->isBlockSent(block_pos) || (player && - player_pos.getDistanceFrom(intToFloat(pos, BS)) > maxd)) { + if (!client->isBlockSent(block_pos) || + player_pos.getDistanceFrom(pos) > far_d_nodes) { client->SetBlockNotSent(block_pos); continue; } @@ -2314,38 +2326,49 @@ void Server::sendMetadataChanged(const std::list &meta_updates, float far continue; // Send the meta changes - std::ostringstream os(std::ios::binary); - meta_updates_list.serialize(os, client->net_proto_version, false, true); - std::ostringstream oss(std::ios::binary); - compressZlib(os.str(), oss); - - NetworkPacket pkt(TOCLIENT_NODEMETA_CHANGED, 0); - pkt.putLongString(oss.str()); - m_clients.send(i, 0, &pkt, true); + os.str(""); + meta_updates_list.serialize(os, client->serialization_version, false, true, true); + std::string raw = os.str(); + os.str(""); + compressZlib(raw, os); + + NetworkPacket pkt(TOCLIENT_NODEMETA_CHANGED, 0, i); + pkt.putLongString(os.str()); + Send(&pkt); meta_updates_list.clear(); } - - m_clients.unlock(); } void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, - u16 net_proto_version) + u16 net_proto_version, SerializedBlockCache *cache) { - /* - Create a packet with the block in the right format - */ thread_local const int net_compression_level = rangelim(g_settings->getS16("map_compression_level_net"), -1, 9); - std::ostringstream os(std::ios_base::binary); - block->serialize(os, ver, false, net_compression_level); - block->serializeNetworkSpecific(os); - std::string s = os.str(); + std::string s, *sptr = nullptr; - NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + s.size(), peer_id); + if (cache) { + auto it = cache->find({block->getPos(), ver}); + if (it != cache->end()) + sptr = &it->second; + } + + // Serialize the block in the right format + if (!sptr) { + std::ostringstream os(std::ios_base::binary); + block->serialize(os, ver, false, net_compression_level); + block->serializeNetworkSpecific(os); + s = os.str(); + sptr = &s; + } + NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + sptr->size(), peer_id); pkt << block->getPos(); - pkt.putRawString(s.c_str(), s.size()); + pkt.putRawString(*sptr); Send(&pkt); + + // Store away in cache + if (cache && sptr == &s) + (*cache)[{block->getPos(), ver}] = std::move(s); } void Server::SendBlocks(float dtime) @@ -2355,14 +2378,14 @@ void Server::SendBlocks(float dtime) std::vector queue; - u32 total_sending = 0; + u32 total_sending = 0, unique_clients = 0; { ScopeProfiler sp2(g_profiler, "Server::SendBlocks(): Collect list"); std::vector clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); for (const session_t client_id : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(client_id, CS_Active); @@ -2370,9 +2393,10 @@ void Server::SendBlocks(float dtime) continue; total_sending += client->getSendingCount(); + const auto old_count = queue.size(); client->GetNextBlocks(m_env,m_emerge, dtime, queue); + unique_clients += queue.size() > old_count ? 1 : 0; } - m_clients.unlock(); } // Sort. @@ -2380,7 +2404,7 @@ void Server::SendBlocks(float dtime) // Lowest is most important. std::sort(queue.begin(), queue.end()); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); // Maximal total count calculation // The per-client block sends is halved with the maximal online users @@ -2390,6 +2414,12 @@ void Server::SendBlocks(float dtime) ScopeProfiler sp(g_profiler, "Server::SendBlocks(): Send to clients"); Map &map = m_env->getMap(); + SerializedBlockCache cache, *cache_ptr = nullptr; + if (unique_clients > 1) { + // caching is pointless with a single client + cache_ptr = &cache; + } + for (const PrioritySortedBlockTransfer &block_to_send : queue) { if (total_sending >= max_blocks_to_send) break; @@ -2404,12 +2434,11 @@ void Server::SendBlocks(float dtime) continue; SendBlockNoLock(block_to_send.peer_id, block, client->serialization_version, - client->net_proto_version); + client->net_proto_version, cache_ptr); client->SentBlock(block_to_send.pos); total_sending++; } - m_clients.unlock(); } bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos) @@ -2418,15 +2447,12 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos) if (!block) return false; - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); RemoteClient *client = m_clients.lockedGetClientNoEx(peer_id, CS_Active); - if (!client || client->isBlockSent(blockpos)) { - m_clients.unlock(); + if (!client || client->isBlockSent(blockpos)) return false; - } SendBlockNoLock(peer_id, block, client->serialization_version, client->net_proto_version); - m_clients.unlock(); return true; } @@ -2444,9 +2470,8 @@ bool Server::addMediaFile(const std::string &filename, // If name is not in a supported format, ignore it const char *supported_ext[] = { ".png", ".jpg", ".bmp", ".tga", - ".pcx", ".ppm", ".psd", ".wal", ".rgb", ".ogg", - ".x", ".b3d", ".md2", ".obj", + ".x", ".b3d", ".obj", // Custom translation file format ".tr", NULL @@ -2498,7 +2523,9 @@ void Server::fillMediaCache() // Collect all media file paths std::vector paths; - // The paths are ordered in descending priority + + // ordered in descending priority + paths.push_back(getBuiltinLuaPath() + DIR_DELIM + "locale"); fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server"); fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures"); m_modmgr->getModsMediaPaths(paths); @@ -2532,6 +2559,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co std::string lang_suffix; lang_suffix.append(".").append(lang_code).append(".tr"); for (const auto &i : m_media) { + if (i.second.no_announce) + continue; if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix)) continue; media_sent++; @@ -2540,6 +2569,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co pkt << media_sent; for (const auto &i : m_media) { + if (i.second.no_announce) + continue; if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix)) continue; pkt << i.first << i.second.sha1_digest; @@ -2558,11 +2589,9 @@ struct SendableMedia std::string path; std::string data; - SendableMedia(const std::string &name_="", const std::string &path_="", - const std::string &data_=""): - name(name_), - path(path_), - data(data_) + SendableMedia(const std::string &name, const std::string &path, + std::string &&data): + name(name), path(path), data(std::move(data)) {} }; @@ -2589,40 +2618,19 @@ void Server::sendRequestedMedia(session_t peer_id, continue; } - //TODO get path + name - std::string tpath = m_media[name].path; + const auto &m = m_media[name]; // Read data - std::ifstream fis(tpath.c_str(), std::ios_base::binary); - if(!fis.good()){ - errorstream<<"Server::sendRequestedMedia(): Could not open \"" - <= bytes_per_bunch) { @@ -2665,6 +2673,33 @@ void Server::sendRequestedMedia(session_t peer_id, } } +void Server::stepPendingDynMediaCallbacks(float dtime) +{ + MutexAutoLock lock(m_env_mutex); + + for (auto it = m_pending_dyn_media.begin(); it != m_pending_dyn_media.end();) { + it->second.expiry_timer -= dtime; + bool del = it->second.waiting_players.empty() || it->second.expiry_timer < 0; + + if (!del) { + it++; + continue; + } + + const auto &name = it->second.filename; + if (!name.empty()) { + assert(m_media.count(name)); + // if no_announce isn't set we're definitely deleting the wrong file! + sanity_check(m_media[name].no_announce); + + fs::DeleteSingleFileOrEmptyDirectory(m_media[name].path); + m_media.erase(name); + } + getScriptIface()->freeDynamicMediaCallback(it->first); + it = m_pending_dyn_media.erase(it); + } +} + void Server::SendMinimapModes(session_t peer_id, std::vector &modes, size_t wanted_mode) { @@ -2727,23 +2762,18 @@ void Server::sendDetachedInventories(session_t peer_id, bool incremental) Something random */ -void Server::DiePlayer(session_t peer_id, const PlayerHPChangeReason &reason) +void Server::HandlePlayerDeath(PlayerSAO *playersao, const PlayerHPChangeReason &reason) { - PlayerSAO *playersao = getPlayerSAO(peer_id); - assert(playersao); - infostream << "Server::DiePlayer(): Player " << playersao->getPlayer()->getName() << " dies" << std::endl; - playersao->setHP(0, reason); playersao->clearParentAttachment(); // Trigger scripted stuff m_script->on_dieplayer(playersao, reason); - SendPlayerHP(peer_id); - SendDeathscreen(peer_id, false, v3f(0,0,0)); + SendDeathscreen(playersao->getPeerID(), false, v3f(0,0,0)); } void Server::RespawnPlayer(session_t peer_id) @@ -2755,17 +2785,16 @@ void Server::RespawnPlayer(session_t peer_id) << playersao->getPlayer()->getName() << " respawns" << std::endl; - playersao->setHP(playersao->accessObjectProperties()->hp_max, + const auto *prop = playersao->accessObjectProperties(); + playersao->setHP(prop->hp_max, PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN)); - playersao->setBreath(playersao->accessObjectProperties()->breath_max); + playersao->setBreath(prop->breath_max); bool repositioned = m_script->on_respawnplayer(playersao); if (!repositioned) { // setPos will send the new position to client playersao->setPos(findSpawnPos()); } - - SendPlayerHP(peer_id); } @@ -2776,29 +2805,10 @@ void Server::DenySudoAccess(session_t peer_id) } -void Server::DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason, bool reconnect) -{ - SendAccessDenied(peer_id, reason, str_reason, reconnect); - - m_clients.event(peer_id, CSE_SetDenied); - DisconnectPeer(peer_id); -} - - void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason) -{ - SendAccessDenied(peer_id, reason, custom_reason); - m_clients.event(peer_id, CSE_SetDenied); - DisconnectPeer(peer_id); -} - -// 13/03/15: remove this function when protocol version 25 will become -// the minimum version for MT users, maybe in 1 year -void Server::DenyAccess_Legacy(session_t peer_id, const std::wstring &reason) + const std::string &custom_reason, bool reconnect) { - SendAccessDenied_Legacy(peer_id, reason); + SendAccessDenied(peer_id, reason, custom_reason, reconnect); m_clients.event(peer_id, CSE_SetDenied); DisconnectPeer(peer_id); } @@ -2964,7 +2974,7 @@ void Server::handleChatInterfaceEvent(ChatEvent *evt) } } -std::wstring Server::handleChat(const std::string &name, const std::wstring &wname, +std::wstring Server::handleChat(const std::string &name, std::wstring wmessage, bool check_shout_priv, RemotePlayer *player) { // If something goes wrong, this player is to blame @@ -2984,8 +2994,8 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna return ws.str(); } case RPLAYER_CHATRESULT_KICK: - DenyAccess_Legacy(player->getPeerId(), - L"You have been kicked due to message flooding."); + DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, + "You have been kicked due to message flooding."); return L""; case RPLAYER_CHATRESULT_OK: break; @@ -3001,8 +3011,11 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna } auto message = trim(wide_to_utf8(wmessage)); + if (message.empty()) + return L""; + if (message.find_first_of("\n\r") != std::wstring::npos) { - return L"New lines are not permitted in chat messages"; + return L"Newlines are not permitted in chat messages"; } // Run script hook, exit if script ate the chat message @@ -3023,10 +3036,10 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna the Cyrillic alphabet and some characters on older Android devices */ #ifdef __ANDROID__ - line += L"<" + wname + L"> " + wmessage; + line += L"<" + utf8_to_wide(name) + L"> " + wmessage; #else - line += narrow_to_wide(m_script->formatChatMessage(name, - wide_to_narrow(wmessage))); + line += utf8_to_wide(m_script->formatChatMessage(name, + wide_to_utf8(wmessage))); #endif } @@ -3039,35 +3052,23 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna /* Send the message to others */ - actionstream << "CHAT: " << wide_to_narrow(unescape_enriched(line)) << std::endl; - - std::vector clients = m_clients.getClientIDs(); - - /* - Send the message back to the inital sender - if they are using protocol version >= 29 - */ + actionstream << "CHAT: " << wide_to_utf8(unescape_enriched(line)) << std::endl; - session_t peer_id_to_avoid_sending = - (player ? player->getPeerId() : PEER_ID_INEXISTENT); + ChatMessage chatmsg(line); - if (player && player->protocol_version >= 29) - peer_id_to_avoid_sending = PEER_ID_INEXISTENT; + std::vector clients = m_clients.getClientIDs(); + for (u16 cid : clients) + SendChatMessage(cid, chatmsg); - for (u16 cid : clients) { - if (cid != peer_id_to_avoid_sending) - SendChatMessage(cid, ChatMessage(line)); - } return L""; } void Server::handleAdminChat(const ChatEventChat *evt) { std::string name = evt->nick; - std::wstring wname = utf8_to_wide(name); std::wstring wmessage = evt->evt_msg; - std::wstring answer = handleChat(name, wname, wmessage); + std::wstring answer = handleChat(name, wmessage); // If asked to send answer to sender if (!answer.empty()) { @@ -3104,46 +3105,45 @@ PlayerSAO *Server::getPlayerSAO(session_t peer_id) return player->getPlayerSAO(); } -std::wstring Server::getStatusString() +std::string Server::getStatusString() { - std::wostringstream os(std::ios_base::binary); - os << L"# Server: "; + std::ostringstream os(std::ios_base::binary); + os << "# Server: "; // Version - os << L"version=" << narrow_to_wide(g_version_string); + os << "version: " << g_version_string; + // Game + os << " | game: " << (m_gamespec.title.empty() ? m_gamespec.id : m_gamespec.title); // Uptime - os << L", uptime=" << m_uptime_counter->get(); + os << " | uptime: " << duration_to_string((int) m_uptime_counter->get()); // Max lag estimate - os << L", max_lag=" << (m_env ? m_env->getMaxLagEstimate() : 0); + os << " | max lag: " << std::setprecision(3); + os << (m_env ? m_env->getMaxLagEstimate() : 0) << "s"; // Information about clients bool first = true; - os << L", clients={"; + os << " | clients: "; if (m_env) { std::vector clients = m_clients.getClientIDs(); for (session_t client_id : clients) { RemotePlayer *player = m_env->getPlayer(client_id); // Get name of player - std::wstring name = L"unknown"; - if (player) - name = narrow_to_wide(player->getName()); + const char *name = player ? player->getName() : ""; // Add name to information string if (!first) - os << L", "; + os << ", "; else first = false; - os << name; } } - os << L"}"; if (m_env && !((ServerMap*)(&m_env->getMap()))->isSavingEnabled()) - os << std::endl << L"# Server: " << " WARNING: Map saving is disabled."; + os << std::endl << "# Server: " << " WARNING: Map saving is disabled."; if (!g_settings->get("motd").empty()) - os << std::endl << L"# Server: " << narrow_to_wide(g_settings->get("motd")); + os << std::endl << "# Server: " << g_settings->get("motd"); return os.str(); } @@ -3291,9 +3291,12 @@ bool Server::hudSetFlags(RemotePlayer *player, u32 flags, u32 mask) if (!player) return false; + u32 new_hud_flags = (player->hud_flags & ~mask) | flags; + if (new_hud_flags == player->hud_flags) // no change + return true; + SendHUDSetFlags(player->getPeerId(), flags, mask); - player->hud_flags &= ~mask; - player->hud_flags |= flags; + player->hud_flags = new_hud_flags; PlayerSAO* playersao = player->getPlayerSAO(); @@ -3339,7 +3342,8 @@ void Server::hudSetHotbarSelectedImage(RemotePlayer *player, const std::string & Address Server::getPeerAddress(session_t peer_id) { - return m_con->GetPeerAddress(peer_id); + // Note that this is only set after Init was received in Server::handleCommand_Init + return getClient(peer_id, CS_Invalid)->getAddress(); } void Server::setLocalPlayerAnimations(RemotePlayer *player, @@ -3401,6 +3405,13 @@ void Server::overrideDayNightRatio(RemotePlayer *player, bool do_override, SendOverrideDayNightRatio(player->getPeerId(), do_override, ratio); } +void Server::setLighting(RemotePlayer *player, const Lighting &lighting) +{ + sanity_check(player); + player->setLighting(lighting); + SendSetLighting(player->getPeerId(), lighting); +} + void Server::notifyPlayers(const std::wstring &msg) { SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(msg)); @@ -3473,13 +3484,18 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id) SendDeleteParticleSpawner(peer_id, id); } -bool Server::dynamicAddMedia(const std::string &filepath) +bool Server::dynamicAddMedia(std::string filepath, + const u32 token, const std::string &to_player, bool ephemeral) { std::string filename = fs::GetFilenameFromPath(filepath.c_str()); - if (m_media.find(filename) != m_media.end()) { - errorstream << "Server::dynamicAddMedia(): file \"" << filename - << "\" already exists in media cache" << std::endl; - return false; + auto it = m_media.find(filename); + if (it != m_media.end()) { + // Allow the same path to be "added" again in certain conditions + if (ephemeral || it->second.path != filepath) { + errorstream << "Server::dynamicAddMedia(): file \"" << filename + << "\" already exists in media cache" << std::endl; + return false; + } } // Load the file and add it to our media cache @@ -3488,26 +3504,106 @@ bool Server::dynamicAddMedia(const std::string &filepath) if (!ok) return false; + if (ephemeral) { + // Create a copy of the file and swap out the path, this removes the + // requirement that mods keep the file accessible at the original path. + filepath = fs::CreateTempFile(); + bool ok = ([&] () -> bool { + if (filepath.empty()) + return false; + std::ofstream os(filepath.c_str(), std::ios::binary); + if (!os.good()) + return false; + os << filedata; + os.close(); + return !os.fail(); + })(); + if (!ok) { + errorstream << "Server: failed to create a copy of media file " + << "\"" << filename << "\"" << std::endl; + m_media.erase(filename); + return false; + } + verbosestream << "Server: \"" << filename << "\" temporarily copied to " + << filepath << std::endl; + + m_media[filename].path = filepath; + m_media[filename].no_announce = true; + // stepPendingDynMediaCallbacks will clean this up later. + } else if (!to_player.empty()) { + m_media[filename].no_announce = true; + } + // Push file to existing clients NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0); - pkt << raw_hash << filename << (bool) true; - pkt.putLongString(filedata); + pkt << raw_hash << filename << (bool)ephemeral; - auto client_ids = m_clients.getClientIDs(CS_DefinitionsSent); - for (session_t client_id : client_ids) { - /* - The network layer only guarantees ordered delivery inside a channel. - Since the very next packet could be one that uses the media, we have - to push the media over ALL channels to ensure it is processed before - it is used. - In practice this means we have to send it twice: - - channel 1 (HUD) - - channel 0 (everything else: e.g. play_sound, object messages) - */ - m_clients.send(client_id, 1, &pkt, true); - m_clients.send(client_id, 0, &pkt, true); + NetworkPacket legacy_pkt = pkt; + + // Newer clients get asked to fetch the file (asynchronous) + pkt << token; + // Older clients have an awful hack that just throws the data at them + legacy_pkt.putLongString(filedata); + + std::unordered_set delivered, waiting; + { + ClientInterface::AutoLock clientlock(m_clients); + for (auto &pair : m_clients.getClientList()) { + if (pair.second->getState() == CS_DefinitionsSent && !ephemeral) { + /* + If a client is in the DefinitionsSent state it is too late to + transfer the file via sendMediaAnnouncement() but at the same + time the client cannot accept a media push yet. + Short of artificially delaying the joining process there is no + way for the server to resolve this so we (currently) opt not to. + */ + warningstream << "The media \"" << filename << "\" (dynamic) could " + "not be delivered to " << pair.second->getName() + << " due to a race condition." << std::endl; + continue; + } + if (pair.second->getState() < CS_Active) + continue; + + const auto proto_ver = pair.second->net_proto_version; + if (proto_ver < 39) + continue; + + const session_t peer_id = pair.second->peer_id; + if (!to_player.empty() && getPlayerName(peer_id) != to_player) + continue; + + if (proto_ver < 40) { + delivered.emplace(peer_id); + /* + The network layer only guarantees ordered delivery inside a channel. + Since the very next packet could be one that uses the media, we have + to push the media over ALL channels to ensure it is processed before + it is used. In practice this means channels 1 and 0. + */ + m_clients.send(peer_id, 1, &legacy_pkt, true); + m_clients.send(peer_id, 0, &legacy_pkt, true); + } else { + waiting.emplace(peer_id); + Send(peer_id, &pkt); + } + } + } + + // Run callback for players that already had the file delivered (legacy-only) + for (session_t peer_id : delivered) { + if (auto player = m_env->getPlayer(peer_id)) + getScriptIface()->on_dynamic_media_added(token, player->getName()); } + // Save all others in our pending state + auto &state = m_pending_dyn_media[token]; + state.waiting_players = std::move(waiting); + // regardless of success throw away the callback after a while + state.expiry_timer = 60.0f; + if (ephemeral) + state.filename = filename; + return true; } @@ -3602,21 +3698,11 @@ const ModSpec *Server::getModSpec(const std::string &modname) const return m_modmgr->getModSpec(modname); } -void Server::getModNames(std::vector &modlist) -{ - m_modmgr->getModNames(modlist); -} - std::string Server::getBuiltinLuaPath() { return porting::path_share + DIR_DELIM + "builtin"; } -std::string Server::getModStoragePath() const -{ - return m_path_world + DIR_DELIM + "mod_storage"; -} - v3f Server::findSpawnPos() { ServerMap &map = m_env->getServerMap(); @@ -3780,11 +3866,8 @@ bool Server::registerModStorage(ModMetadata *storage) void Server::unregisterModStorage(const std::string &name) { std::unordered_map::const_iterator it = m_mod_storages.find(name); - if (it != m_mod_storages.end()) { - // Save unconditionaly on unregistration - it->second->save(getModStoragePath()); + if (it != m_mod_storages.end()) m_mod_storages.erase(name); - } } void dedicated_server_loop(Server &server, bool &kill) @@ -3922,3 +4005,106 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code) return translations; } + +ModMetadataDatabase *Server::openModStorageDatabase(const std::string &world_path) +{ + std::string world_mt_path = world_path + DIR_DELIM + "world.mt"; + Settings world_mt; + if (!world_mt.readConfigFile(world_mt_path.c_str())) + throw BaseException("Cannot read world.mt!"); + + std::string backend = world_mt.exists("mod_storage_backend") ? + world_mt.get("mod_storage_backend") : "files"; + if (backend == "files") + warningstream << "/!\\ You are using the old mod storage files backend. " + << "This backend is deprecated and may be removed in a future release /!\\" + << std::endl << "Switching to SQLite3 is advised, " + << "please read http://wiki.minetest.net/Database_backends." << std::endl; + + return openModStorageDatabase(backend, world_path, world_mt); +} + +ModMetadataDatabase *Server::openModStorageDatabase(const std::string &backend, + const std::string &world_path, const Settings &world_mt) +{ + if (backend == "sqlite3") + return new ModMetadataDatabaseSQLite3(world_path); + + if (backend == "files") + return new ModMetadataDatabaseFiles(world_path); + + if (backend == "dummy") + return new Database_Dummy(); + + throw BaseException("Mod storage database backend " + backend + " not supported"); +} + +bool Server::migrateModStorageDatabase(const GameParams &game_params, const Settings &cmd_args) +{ + std::string migrate_to = cmd_args.get("migrate-mod-storage"); + Settings world_mt; + std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; + if (!world_mt.readConfigFile(world_mt_path.c_str())) { + errorstream << "Cannot read world.mt!" << std::endl; + return false; + } + + std::string backend = world_mt.exists("mod_storage_backend") ? + world_mt.get("mod_storage_backend") : "files"; + if (backend == migrate_to) { + errorstream << "Cannot migrate: new backend is same" + << " as the old one" << std::endl; + return false; + } + + ModMetadataDatabase *srcdb = nullptr; + ModMetadataDatabase *dstdb = nullptr; + + bool succeeded = false; + + try { + srcdb = Server::openModStorageDatabase(backend, game_params.world_path, world_mt); + dstdb = Server::openModStorageDatabase(migrate_to, game_params.world_path, world_mt); + + dstdb->beginSave(); + + std::vector mod_list; + srcdb->listMods(&mod_list); + for (const std::string &modname : mod_list) { + StringMap meta; + srcdb->getModEntries(modname, &meta); + for (const auto &pair : meta) { + dstdb->setModEntry(modname, pair.first, pair.second); + } + } + + dstdb->endSave(); + + succeeded = true; + + actionstream << "Successfully migrated the metadata of " + << mod_list.size() << " mods" << std::endl; + world_mt.set("mod_storage_backend", migrate_to); + if (!world_mt.updateConfigFile(world_mt_path.c_str())) + errorstream << "Failed to update world.mt!" << std::endl; + else + actionstream << "world.mt updated" << std::endl; + + } catch (BaseException &e) { + errorstream << "An error occurred during migration: " << e.what() << std::endl; + } + + delete srcdb; + delete dstdb; + + if (succeeded && backend == "files") { + // Back up files + const std::string storage_path = game_params.world_path + DIR_DELIM + "mod_storage"; + const std::string backup_path = game_params.world_path + DIR_DELIM + "mod_storage.bak"; + if (!fs::Rename(storage_path, backup_path)) + warningstream << "After migration, " << storage_path + << " could not be renamed to " << backup_path << std::endl; + } + + return succeeded; +}