forked from Mirrorlandia_minetest/minetest
Server pushing media at runtime (#9961)
This commit is contained in:
parent
982a030f33
commit
2424dfe007
@ -5217,6 +5217,20 @@ Server
|
||||
* Returns a code (0: successful, 1: no such player, 2: player is connected)
|
||||
* `minetest.remove_player_auth(name)`: remove player authentication data
|
||||
* Returns boolean indicating success (false if player nonexistant)
|
||||
* `minetest.dynamic_add_media(filepath)`
|
||||
* Adds the file at the given path to the media sent to clients by the server
|
||||
on startup and also pushes this file to already connected clients.
|
||||
The file must be a supported image, sound or model format. It must not be
|
||||
modified, deleted, moved or renamed after calling this function.
|
||||
The list of dynamically added media is not persisted.
|
||||
* Returns boolean indicating success (duplicate files count as error)
|
||||
* The media will be ready to use (in e.g. entity textures, sound_play)
|
||||
immediately after calling this function.
|
||||
Old clients that lack support for this feature will not see the media
|
||||
unless they reconnect to the server.
|
||||
* Since media transferred this way does not use client caching or HTTP
|
||||
transfers, dynamic media should not be used with big files or performance
|
||||
will suffer.
|
||||
|
||||
Bans
|
||||
----
|
||||
|
@ -670,11 +670,9 @@ void Client::step(float dtime)
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::loadMedia(const std::string &data, const std::string &filename)
|
||||
bool Client::loadMedia(const std::string &data, const std::string &filename,
|
||||
bool from_media_push)
|
||||
{
|
||||
// Silly irrlicht's const-incorrectness
|
||||
Buffer<char> data_rw(data.c_str(), data.size());
|
||||
|
||||
std::string name;
|
||||
|
||||
const char *image_ext[] = {
|
||||
@ -690,6 +688,9 @@ bool Client::loadMedia(const std::string &data, const std::string &filename)
|
||||
io::IFileSystem *irrfs = RenderingEngine::get_filesystem();
|
||||
video::IVideoDriver *vdrv = RenderingEngine::get_video_driver();
|
||||
|
||||
// Silly irrlicht's const-incorrectness
|
||||
Buffer<char> data_rw(data.c_str(), data.size());
|
||||
|
||||
// Create an irrlicht memory file
|
||||
io::IReadFile *rfile = irrfs->createMemoryReadFile(
|
||||
*data_rw, data_rw.getSize(), "_tempreadfile");
|
||||
@ -727,7 +728,6 @@ bool Client::loadMedia(const std::string &data, const std::string &filename)
|
||||
".x", ".b3d", ".md2", ".obj",
|
||||
NULL
|
||||
};
|
||||
|
||||
name = removeStringEnd(filename, model_ext);
|
||||
if (!name.empty()) {
|
||||
verbosestream<<"Client: Storing model into memory: "
|
||||
@ -744,6 +744,8 @@ bool Client::loadMedia(const std::string &data, const std::string &filename)
|
||||
};
|
||||
name = removeStringEnd(filename, translate_ext);
|
||||
if (!name.empty()) {
|
||||
if (from_media_push)
|
||||
return false;
|
||||
TRACESTREAM(<< "Client: Loading translation: "
|
||||
<< "\"" << filename << "\"" << std::endl);
|
||||
g_client_translations->loadTranslation(data);
|
||||
|
@ -222,6 +222,7 @@ public:
|
||||
void handleCommand_FormspecPrepend(NetworkPacket *pkt);
|
||||
void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt);
|
||||
void handleCommand_PlayerSpeed(NetworkPacket *pkt);
|
||||
void handleCommand_MediaPush(NetworkPacket *pkt);
|
||||
|
||||
void ProcessData(NetworkPacket *pkt);
|
||||
|
||||
@ -376,7 +377,8 @@ public:
|
||||
|
||||
// The following set of functions is used by ClientMediaDownloader
|
||||
// Insert a media file appropriately into the appropriate manager
|
||||
bool loadMedia(const std::string &data, const std::string &filename);
|
||||
bool loadMedia(const std::string &data, const std::string &filename,
|
||||
bool from_media_push = false);
|
||||
// Send a request for conventional media transfer
|
||||
void request_media(const std::vector<std::string> &file_requests);
|
||||
|
||||
@ -488,6 +490,7 @@ private:
|
||||
Camera *m_camera = nullptr;
|
||||
Minimap *m_minimap = nullptr;
|
||||
bool m_minimap_disabled_by_server = false;
|
||||
|
||||
// Server serialization version
|
||||
u8 m_server_ser_ver;
|
||||
|
||||
@ -529,7 +532,6 @@ private:
|
||||
AuthMechanism m_chosen_auth_mech;
|
||||
void *m_auth_data = nullptr;
|
||||
|
||||
|
||||
bool m_access_denied = false;
|
||||
bool m_access_denied_reconnect = false;
|
||||
std::string m_access_denied_reason = "";
|
||||
@ -538,7 +540,10 @@ private:
|
||||
bool m_nodedef_received = false;
|
||||
bool m_activeobjects_received = false;
|
||||
bool m_mods_loaded = false;
|
||||
|
||||
ClientMediaDownloader *m_media_downloader;
|
||||
// Set of media filenames pushed by server at runtime
|
||||
std::unordered_set<std::string> m_media_pushed_files;
|
||||
|
||||
// time_of_day speed approximation for old protocol
|
||||
bool m_time_of_day_set = false;
|
||||
|
@ -35,6 +35,15 @@ static std::string getMediaCacheDir()
|
||||
return porting::path_cache + DIR_DELIM + "media";
|
||||
}
|
||||
|
||||
bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata)
|
||||
{
|
||||
FileCache media_cache(getMediaCacheDir());
|
||||
std::string sha1_hex = hex_encode(raw_hash);
|
||||
if (!media_cache.exists(sha1_hex))
|
||||
return media_cache.update(sha1_hex, filedata);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
ClientMediaDownloader
|
||||
*/
|
||||
@ -559,7 +568,6 @@ bool ClientMediaDownloader::checkAndLoad(
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Minetest Hashset File Format
|
||||
|
||||
|
@ -33,6 +33,11 @@ struct HTTPFetchResult;
|
||||
#define MTHASHSET_FILE_SIGNATURE 0x4d544853 // 'MTHS'
|
||||
#define MTHASHSET_FILE_NAME "index.mth"
|
||||
|
||||
// Store file into media cache (unless it exists already)
|
||||
// Validating the hash is responsibility of the caller
|
||||
bool clientMediaUpdateCache(const std::string &raw_hash,
|
||||
const std::string &filedata);
|
||||
|
||||
class ClientMediaDownloader
|
||||
{
|
||||
public:
|
||||
|
@ -82,8 +82,16 @@ bool FileCache::update(const std::string &name, const std::string &data)
|
||||
std::string path = m_dir + DIR_DELIM + name;
|
||||
return updateByPath(path, data);
|
||||
}
|
||||
|
||||
bool FileCache::load(const std::string &name, std::ostream &os)
|
||||
{
|
||||
std::string path = m_dir + DIR_DELIM + name;
|
||||
return loadByPath(path, os);
|
||||
}
|
||||
|
||||
bool FileCache::exists(const std::string &name)
|
||||
{
|
||||
std::string path = m_dir + DIR_DELIM + name;
|
||||
std::ifstream fis(path.c_str(), std::ios_base::binary);
|
||||
return fis.good();
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ public:
|
||||
|
||||
bool update(const std::string &name, const std::string &data);
|
||||
bool load(const std::string &name, std::ostream &os);
|
||||
bool exists(const std::string &name);
|
||||
|
||||
private:
|
||||
std::string m_dir;
|
||||
|
@ -691,6 +691,12 @@ std::string AbsolutePath(const std::string &path)
|
||||
const char *GetFilenameFromPath(const char *path)
|
||||
{
|
||||
const char *filename = strrchr(path, DIR_DELIM_CHAR);
|
||||
// Consistent with IsDirDelimiter this function handles '/' too
|
||||
if (DIR_DELIM_CHAR != '/') {
|
||||
const char *tmp = strrchr(path, '/');
|
||||
if (tmp && tmp > filename)
|
||||
filename = tmp;
|
||||
}
|
||||
return filename ? filename + 1 : path;
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] =
|
||||
{ "TOCLIENT_TIME_OF_DAY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_TimeOfDay }, // 0x29
|
||||
{ "TOCLIENT_CSM_RESTRICTION_FLAGS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CSMRestrictionFlags }, // 0x2A
|
||||
{ "TOCLIENT_PLAYER_SPEED", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_PlayerSpeed }, // 0x2B
|
||||
null_command_handler,
|
||||
{ "TOCLIENT_MEDIA_PUSH", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MediaPush }, // 0x2C
|
||||
null_command_handler,
|
||||
null_command_handler,
|
||||
{ "TOCLIENT_CHAT_MESSAGE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ChatMessage }, // 0x2F
|
||||
|
@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "script/scripting_client.h"
|
||||
#include "util/serialize.h"
|
||||
#include "util/srp.h"
|
||||
#include "util/sha1.h"
|
||||
#include "tileanimation.h"
|
||||
#include "gettext.h"
|
||||
#include "skyparams.h"
|
||||
@ -1471,6 +1472,51 @@ void Client::handleCommand_PlayerSpeed(NetworkPacket *pkt)
|
||||
player->addVelocity(added_vel);
|
||||
}
|
||||
|
||||
void Client::handleCommand_MediaPush(NetworkPacket *pkt)
|
||||
{
|
||||
std::string raw_hash, filename, filedata;
|
||||
bool cached;
|
||||
|
||||
*pkt >> raw_hash >> filename >> cached;
|
||||
filedata = pkt->readLongString();
|
||||
|
||||
if (raw_hash.size() != 20 || filedata.empty() || filename.empty() ||
|
||||
!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) {
|
||||
throw PacketError("Illegal filename, data or hash");
|
||||
}
|
||||
|
||||
verbosestream << "Server pushes media file \"" << filename << "\" with "
|
||||
<< filedata.size() << " bytes of data (cached=" << cached
|
||||
<< ")" << std::endl;
|
||||
|
||||
if (m_media_pushed_files.count(filename) != 0) {
|
||||
// Silently ignore for synchronization purposes
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute and check checksum of data
|
||||
std::string computed_hash;
|
||||
{
|
||||
SHA1 ctx;
|
||||
ctx.addBytes(filedata.c_str(), filedata.size());
|
||||
unsigned char *buf = ctx.getDigest();
|
||||
computed_hash.assign((char*) buf, 20);
|
||||
free(buf);
|
||||
}
|
||||
if (raw_hash != computed_hash) {
|
||||
verbosestream << "Hash of file data mismatches, ignoring." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Actually load media
|
||||
loadMedia(filedata, filename, true);
|
||||
m_media_pushed_files.insert(filename);
|
||||
|
||||
// Cache file for the next time when this client joins the same server
|
||||
if (cached)
|
||||
clientMediaUpdateCache(raw_hash, filedata);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mod channels
|
||||
*/
|
||||
|
@ -323,6 +323,15 @@ enum ToClientCommand
|
||||
v3f added_vel
|
||||
*/
|
||||
|
||||
TOCLIENT_MEDIA_PUSH = 0x2C,
|
||||
/*
|
||||
std::string raw_hash
|
||||
std::string filename
|
||||
bool should_be_cached
|
||||
u32 len
|
||||
char filedata[len]
|
||||
*/
|
||||
|
||||
// (oops, there is some gap here)
|
||||
|
||||
TOCLIENT_CHAT_MESSAGE = 0x2F,
|
||||
|
@ -167,7 +167,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
|
||||
{ "TOCLIENT_TIME_OF_DAY", 0, true }, // 0x29
|
||||
{ "TOCLIENT_CSM_RESTRICTION_FLAGS", 0, true }, // 0x2A
|
||||
{ "TOCLIENT_PLAYER_SPEED", 0, true }, // 0x2B
|
||||
null_command_factory, // 0x2C
|
||||
{ "TOCLIENT_MEDIA_PUSH", 0, true }, // 0x2C (sent over channel 1 too)
|
||||
null_command_factory, // 0x2D
|
||||
null_command_factory, // 0x2E
|
||||
{ "TOCLIENT_CHAT_MESSAGE", 0, true }, // 0x2F
|
||||
|
@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "common/c_converter.h"
|
||||
#include "common/c_content.h"
|
||||
#include "cpp_api/s_base.h"
|
||||
#include "cpp_api/s_security.h"
|
||||
#include "server.h"
|
||||
#include "environment.h"
|
||||
#include "remoteplayer.h"
|
||||
@ -412,9 +413,6 @@ int ModApiServer::l_get_modnames(lua_State *L)
|
||||
std::vector<std::string> modlist;
|
||||
getServer(L)->getModNames(modlist);
|
||||
|
||||
// Take unsorted items from mods_unsorted and sort them into
|
||||
// mods_sorted; not great performance but the number of mods on a
|
||||
// server will likely be small.
|
||||
std::sort(modlist.begin(), modlist.end());
|
||||
|
||||
// Package them up for Lua
|
||||
@ -474,6 +472,23 @@ int ModApiServer::l_sound_fade(lua_State *L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// dynamic_add_media(filepath)
|
||||
int ModApiServer::l_dynamic_add_media(lua_State *L)
|
||||
{
|
||||
NO_MAP_LOCK_REQUIRED;
|
||||
|
||||
// Reject adding media before the server has started up
|
||||
if (!getEnv(L))
|
||||
throw LuaError("Dynamic media cannot be added before server has started up");
|
||||
|
||||
std::string filepath = readParam<std::string>(L, 1);
|
||||
CHECK_SECURE_PATH(L, filepath.c_str(), false);
|
||||
|
||||
bool ok = getServer(L)->dynamicAddMedia(filepath);
|
||||
lua_pushboolean(L, ok);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// is_singleplayer()
|
||||
int ModApiServer::l_is_singleplayer(lua_State *L)
|
||||
{
|
||||
@ -538,6 +553,7 @@ void ModApiServer::Initialize(lua_State *L, int top)
|
||||
API_FCT(sound_play);
|
||||
API_FCT(sound_stop);
|
||||
API_FCT(sound_fade);
|
||||
API_FCT(dynamic_add_media);
|
||||
|
||||
API_FCT(get_player_information);
|
||||
API_FCT(get_player_privs);
|
||||
|
@ -70,6 +70,9 @@ private:
|
||||
// sound_fade(handle, step, gain)
|
||||
static int l_sound_fade(lua_State *L);
|
||||
|
||||
// dynamic_add_media(filepath)
|
||||
static int l_dynamic_add_media(lua_State *L);
|
||||
|
||||
// get_player_privs(name, text)
|
||||
static int l_get_player_privs(lua_State *L);
|
||||
|
||||
|
195
src/server.cpp
195
src/server.cpp
@ -2405,9 +2405,87 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Server::addMediaFile(const std::string &filename,
|
||||
const std::string &filepath, std::string *filedata_to,
|
||||
std::string *digest_to)
|
||||
{
|
||||
// If name contains illegal characters, ignore the file
|
||||
if (!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) {
|
||||
infostream << "Server: ignoring illegal file name: \""
|
||||
<< filename << "\"" << std::endl;
|
||||
return false;
|
||||
}
|
||||
// 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",
|
||||
// Custom translation file format
|
||||
".tr",
|
||||
NULL
|
||||
};
|
||||
if (removeStringEnd(filename, supported_ext).empty()) {
|
||||
infostream << "Server: ignoring unsupported file extension: \""
|
||||
<< filename << "\"" << std::endl;
|
||||
return false;
|
||||
}
|
||||
// Ok, attempt to load the file and add to cache
|
||||
|
||||
// Read data
|
||||
std::ifstream fis(filepath.c_str(), std::ios_base::binary);
|
||||
if (!fis.good()) {
|
||||
errorstream << "Server::addMediaFile(): Could not open \""
|
||||
<< filename << "\" for reading" << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::string filedata;
|
||||
bool bad = false;
|
||||
for (;;) {
|
||||
char buf[1024];
|
||||
fis.read(buf, sizeof(buf));
|
||||
std::streamsize len = fis.gcount();
|
||||
filedata.append(buf, len);
|
||||
if (fis.eof())
|
||||
break;
|
||||
if (!fis.good()) {
|
||||
bad = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bad) {
|
||||
errorstream << "Server::addMediaFile(): Failed to read \""
|
||||
<< filename << "\"" << std::endl;
|
||||
return false;
|
||||
} else if (filedata.empty()) {
|
||||
errorstream << "Server::addMediaFile(): Empty file \""
|
||||
<< filepath << "\"" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
SHA1 sha1;
|
||||
sha1.addBytes(filedata.c_str(), filedata.length());
|
||||
|
||||
unsigned char *digest = sha1.getDigest();
|
||||
std::string sha1_base64 = base64_encode(digest, 20);
|
||||
std::string sha1_hex = hex_encode((char*) digest, 20);
|
||||
if (digest_to)
|
||||
*digest_to = std::string((char*) digest, 20);
|
||||
free(digest);
|
||||
|
||||
// Put in list
|
||||
m_media[filename] = MediaInfo(filepath, sha1_base64);
|
||||
verbosestream << "Server: " << sha1_hex << " is " << filename
|
||||
<< std::endl;
|
||||
|
||||
if (filedata_to)
|
||||
*filedata_to = std::move(filedata);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Server::fillMediaCache()
|
||||
{
|
||||
infostream<<"Server: Calculating media file checksums"<<std::endl;
|
||||
infostream << "Server: Calculating media file checksums" << std::endl;
|
||||
|
||||
// Collect all media file paths
|
||||
std::vector<std::string> paths;
|
||||
@ -2419,80 +2497,15 @@ void Server::fillMediaCache()
|
||||
for (const std::string &mediapath : paths) {
|
||||
std::vector<fs::DirListNode> dirlist = fs::GetDirListing(mediapath);
|
||||
for (const fs::DirListNode &dln : dirlist) {
|
||||
if (dln.dir) // Ignode dirs
|
||||
if (dln.dir) // Ignore dirs
|
||||
continue;
|
||||
std::string filename = dln.name;
|
||||
// If name contains illegal characters, ignore the file
|
||||
if (!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) {
|
||||
infostream<<"Server: ignoring illegal file name: \""
|
||||
<< filename << "\"" << std::endl;
|
||||
continue;
|
||||
}
|
||||
// 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",
|
||||
// Custom translation file format
|
||||
".tr",
|
||||
NULL
|
||||
};
|
||||
if (removeStringEnd(filename, supported_ext).empty()){
|
||||
infostream << "Server: ignoring unsupported file extension: \""
|
||||
<< filename << "\"" << std::endl;
|
||||
continue;
|
||||
}
|
||||
// Ok, attempt to load the file and add to cache
|
||||
std::string filepath;
|
||||
filepath.append(mediapath).append(DIR_DELIM).append(filename);
|
||||
|
||||
// Read data
|
||||
std::ifstream fis(filepath.c_str(), std::ios_base::binary);
|
||||
if (!fis.good()) {
|
||||
errorstream << "Server::fillMediaCache(): Could not open \""
|
||||
<< filename << "\" for reading" << std::endl;
|
||||
continue;
|
||||
}
|
||||
std::ostringstream tmp_os(std::ios_base::binary);
|
||||
bool bad = false;
|
||||
for(;;) {
|
||||
char buf[1024];
|
||||
fis.read(buf, 1024);
|
||||
std::streamsize len = fis.gcount();
|
||||
tmp_os.write(buf, len);
|
||||
if (fis.eof())
|
||||
break;
|
||||
if (!fis.good()) {
|
||||
bad = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(bad) {
|
||||
errorstream<<"Server::fillMediaCache(): Failed to read \""
|
||||
<< filename << "\"" << std::endl;
|
||||
continue;
|
||||
}
|
||||
if(tmp_os.str().length() == 0) {
|
||||
errorstream << "Server::fillMediaCache(): Empty file \""
|
||||
<< filepath << "\"" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
SHA1 sha1;
|
||||
sha1.addBytes(tmp_os.str().c_str(), tmp_os.str().length());
|
||||
|
||||
unsigned char *digest = sha1.getDigest();
|
||||
std::string sha1_base64 = base64_encode(digest, 20);
|
||||
std::string sha1_hex = hex_encode((char*)digest, 20);
|
||||
free(digest);
|
||||
|
||||
// Put in list
|
||||
m_media[filename] = MediaInfo(filepath, sha1_base64);
|
||||
verbosestream << "Server: " << sha1_hex << " is " << filename
|
||||
<< std::endl;
|
||||
std::string filepath = mediapath;
|
||||
filepath.append(DIR_DELIM).append(dln.name);
|
||||
addMediaFile(dln.name, filepath);
|
||||
}
|
||||
}
|
||||
|
||||
infostream << "Server: " << m_media.size() << " media files collected" << std::endl;
|
||||
}
|
||||
|
||||
void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code)
|
||||
@ -3428,6 +3441,44 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id)
|
||||
SendDeleteParticleSpawner(peer_id, id);
|
||||
}
|
||||
|
||||
bool Server::dynamicAddMedia(const std::string &filepath)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// Load the file and add it to our media cache
|
||||
std::string filedata, raw_hash;
|
||||
bool ok = addMediaFile(filename, filepath, &filedata, &raw_hash);
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
// Push file to existing clients
|
||||
NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0);
|
||||
pkt << raw_hash << filename << (bool) true;
|
||||
pkt.putLongString(filedata);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// actions: time-reversed list
|
||||
// Return value: success/failure
|
||||
bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions,
|
||||
|
@ -236,6 +236,8 @@ public:
|
||||
|
||||
void deleteParticleSpawner(const std::string &playername, u32 id);
|
||||
|
||||
bool dynamicAddMedia(const std::string &filepath);
|
||||
|
||||
ServerInventoryManager *getInventoryMgr() const { return m_inventory_mgr.get(); }
|
||||
void sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id);
|
||||
|
||||
@ -435,6 +437,8 @@ private:
|
||||
// Sends blocks to clients (locks env and con on its own)
|
||||
void SendBlocks(float dtime);
|
||||
|
||||
bool addMediaFile(const std::string &filename, const std::string &filepath,
|
||||
std::string *filedata = nullptr, std::string *digest = nullptr);
|
||||
void fillMediaCache();
|
||||
void sendMediaAnnouncement(session_t peer_id, const std::string &lang_code);
|
||||
void sendRequestedMedia(session_t peer_id,
|
||||
|
Loading…
Reference in New Issue
Block a user