aboutsummaryrefslogtreecommitdiff
path: root/advtrains_train_subway/textures/advtrains_subway_wagon_line9.png
blob: a79d7a760046f11fb37c55549e23db62a99378af (plain)
ofshex dumpascii
0000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 02 00 00 00 02 00 08 06 00 00 00 f4 78 d4 .PNG........IHDR..............x.
0020 fa 00 00 00 06 62 4b 47 44 00 ff 00 ff 00 ff a0 bd a7 93 00 00 00 09 70 48 59 73 00 00 0b 13 00 .....bKGD..............pHYs.....
0040 00 0b 13 01 00 9a 9c 18 00 00 00 07 74 49 4d 45 07 e1 0a 17 0a 3a 15 d3 51 aa 44 00 00 04 60 49 ............tIME.....:..Q.D...`I
0060 44 41 54 78 da ed da 31 12 40 30 10 05 50 31 ee c5 09 38 2b 27 e0 64 d1 9a c1 68 24 c5 e6 bd 5a DATx...1.@0..P1...8+'.d...h$...Z
0080 f3 b7 d9 6f 27 5d 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...o']..........................
00a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
00c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
00e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
01a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
01c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
01e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 9f 92 11 50 dc be e6 cf 6f a6 25 c9 .......................P....o.%.
0200 05 50 cf 60 04 d4 92 c7 f9 de 40 8f 4d 2e 00 05 80 e8 ae 8b f1 69 71 ca 05 a0 00 d0 c8 15 40 2e .P.`......@.M........iq.......@.
0220 00 05 80 46 fe fe 23 2d cd a8 b9 80 d8 7a 23 00 00 17 00 28 c6 f9 1f 40 01 a0 41 51 5f c6 7b f1 ...F..#-.....z#....(...@..AQ_.{.
0240 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0260 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0280 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
02a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
02c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
02e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0320 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0360 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0380 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
03a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
03c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
03e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0420 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0440 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0460 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
0480 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
04a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 9b 13 f4 ................................
04c0 f0 21 34 e8 d8 45 b8 00 00 00 00 49 45 4e 44 ae 42 60 82 .!4..E.....IEND.B`.
284'>284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "clientmedia.h"
#include "httpfetch.h"
#include "client.h"
#include "filecache.h"
#include "filesys.h"
#include "log.h"
#include "porting.h"
#include "settings.h"
#include "util/hex.h"
#include "util/serialize.h"
#include "util/sha1.h"
#include "util/string.h"

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
*/

ClientMediaDownloader::ClientMediaDownloader():
	m_media_cache(getMediaCacheDir()),
	m_httpfetch_caller(HTTPFETCH_DISCARD)
{
}

ClientMediaDownloader::~ClientMediaDownloader()
{
	if (m_httpfetch_caller != HTTPFETCH_DISCARD)
		httpfetch_caller_free(m_httpfetch_caller);

	for (auto &file_it : m_files)
		delete file_it.second;

	for (auto &remote : m_remotes)
		delete remote;
}

void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1)
{
	assert(!m_initial_step_done); // pre-condition

	// if name was already announced, ignore the new announcement
	if (m_files.count(name) != 0) {
		errorstream << "Client: ignoring duplicate media announcement "
				<< "sent by server: \"" << name << "\""
				<< std::endl;
		return;
	}

	// if name is empty or contains illegal characters, ignore the file
	if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) {
		errorstream << "Client: ignoring illegal file name "
				<< "sent by server: \"" << name << "\""
				<< std::endl;
		return;
	}

	// length of sha1 must be exactly 20 (160 bits), else ignore the file
	if (sha1.size() != 20) {
		errorstream << "Client: ignoring illegal SHA1 sent by server: "
				<< hex_encode(sha1) << " \"" << name << "\""
				<< std::endl;
		return;
	}

	FileStatus *filestatus = new FileStatus();
	filestatus->received = false;
	filestatus->sha1 = sha1;
	filestatus->current_remote = -1;
	m_files.insert(std::make_pair(name, filestatus));
}

void ClientMediaDownloader::addRemoteServer(const std::string &baseurl)
{
	assert(!m_initial_step_done);	// pre-condition

	#ifdef USE_CURL

	if (g_settings->getBool("enable_remote_media_server")) {
		infostream << "Client: Adding remote server \""
			<< baseurl << "\" for media download" << std::endl;

		RemoteServerStatus *remote = new RemoteServerStatus();
		remote->baseurl = baseurl;
		remote->active_count = 0;
		m_remotes.push_back(remote);
	}

	#else

	infostream << "Client: Ignoring remote server \""
		<< baseurl << "\" because cURL support is not compiled in"
		<< std::endl;

	#endif
}

void ClientMediaDownloader::step(Client *client)
{
	if (!m_initial_step_done) {
		initialStep(client);
		m_initial_step_done = true;
	}

	// Remote media: check for completion of fetches
	if (m_httpfetch_active) {
		bool fetched_something = false;
		HTTPFetchResult fetch_result;

		while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) {
			m_httpfetch_active--;
			fetched_something = true;

			// Is this a hashset (index.mth) or a media file?
			if (fetch_result.request_id < m_remotes.size())
				remoteHashSetReceived(fetch_result);
			else
				remoteMediaReceived(fetch_result, client);
		}

		if (fetched_something)
			startRemoteMediaTransfers();

		// Did all remote transfers end and no new ones can be started?
		// If so, request still missing files from the minetest server
		// (Or report that we have all files.)
		if (m_httpfetch_active == 0) {
			if (m_uncached_received_count < m_uncached_count) {
				infostream << "Client: Failed to remote-fetch "
					<< (m_uncached_count-m_uncached_received_count)
					<< " files. Requesting them"
					<< " the usual way." << std::endl;
			}
			startConventionalTransfers(client);
		}
	}
}

void ClientMediaDownloader::initialStep(Client *client)
{
	// Check media cache
	m_uncached_count = m_files.size();
	for (auto &file_it : m_files) {
		std::string name = file_it.first;
		FileStatus *filestatus = file_it.second;
		const std::string &sha1 = filestatus->sha1;

		std::ostringstream tmp_os(std::ios_base::binary);
		bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os);

		// If found in cache, try to load it from there
		if (found_in_cache) {
			bool success = checkAndLoad(name, sha1,
					tmp_os.str(), true, client);
			if (success) {
				filestatus->received = true;
				m_uncached_count--;
			}
		}
	}

	assert(m_uncached_received_count == 0);

	// Create the media cache dir if we are likely to write to it
	if (m_uncached_count != 0) {
		bool did = fs::CreateAllDirs(getMediaCacheDir());
		if (!did) {
			errorstream << "Client: "
				<< "Could not create media cache directory: "
				<< getMediaCacheDir()
				<< std::endl;
		}
	}

	// If we found all files in the cache, report this fact to the server.
	// If the server reported no remote servers, immediately start
	// conventional transfers. Note: if cURL support is not compiled in,
	// m_remotes is always empty, so "!USE_CURL" is redundant but may
	// reduce the size of the compiled code
	if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) {
		startConventionalTransfers(client);
	}
	else {
		// Otherwise start off by requesting each server's sha1 set

		// This is the first time we use httpfetch, so alloc a caller ID
		m_httpfetch_caller = httpfetch_caller_alloc();

		// Set the active fetch limit to curl_parallel_limit or 84,
		// whichever is greater. This gives us some leeway so that
		// inefficiencies in communicating with the httpfetch thread
		// don't slow down fetches too much. (We still want some limit
		// so that when the first remote server returns its hash set,
		// not all files are requested from that server immediately.)
		// One such inefficiency is that ClientMediaDownloader::step()
		// is only called a couple times per second, while httpfetch
		// might return responses much faster than that.
		// Note that httpfetch strictly enforces curl_parallel_limit
		// but at no inter-thread communication cost. This however
		// doesn't help with the aforementioned inefficiencies.
		// The signifance of 84 is that it is 2*6*9 in base 13.
		m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit");
		m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84);

		// Write a list of hashes that we need. This will be POSTed
		// to the server using Content-Type: application/octet-stream
		std::string required_hash_set = serializeRequiredHashSet();

		// minor fixme: this loop ignores m_httpfetch_active_limit

		// another minor fixme, unlikely to matter in normal usage:
		// these index.mth fetches do (however) count against
		// m_httpfetch_active_limit when starting actual media file
		// requests, so if there are lots of remote servers that are
		// not responding, those will stall new media file transfers.

		for (u32 i = 0; i < m_remotes.size(); ++i) {
			assert(m_httpfetch_next_id == i);

			RemoteServerStatus *remote = m_remotes[i];
			actionstream << "Client: Contacting remote server \""
				<< remote->baseurl << "\"" << std::endl;

			HTTPFetchRequest fetch_request;
			fetch_request.url =
				remote->baseurl + MTHASHSET_FILE_NAME;
			fetch_request.caller = m_httpfetch_caller;
			fetch_request.request_id = m_httpfetch_next_id; // == i
			fetch_request.method = HTTP_POST;
			fetch_request.raw_data = required_hash_set;
			fetch_request.extra_headers.emplace_back(
				"Content-Type: application/octet-stream");

			// Encapsulate possible IPv6 plain address in []
			std::string addr = client->getAddressName();
			if (addr.find(':', 0) != std::string::npos)
				addr = '[' + addr + ']';
			fetch_request.extra_headers.emplace_back(
				std::string("Referer: minetest://") +
				addr + ":" +
				std::to_string(client->getServerAddress().getPort()));

			httpfetch_async(fetch_request);

			m_httpfetch_active++;
			m_httpfetch_next_id++;
			m_outstanding_hash_sets++;
		}
	}
}

void ClientMediaDownloader::remoteHashSetReceived(
		const HTTPFetchResult &fetch_result)
{
	u32 remote_id = fetch_result.request_id;
	assert(remote_id < m_remotes.size());
	RemoteServerStatus *remote = m_remotes[remote_id];

	m_outstanding_hash_sets--;

	if (fetch_result.succeeded) {
		try {
			// Server sent a list of file hashes that are
			// available on it, try to parse the list

			std::set<std::string> sha1_set;
			deSerializeHashSet(fetch_result.data, sha1_set);

			// Parsing succeeded: For every file that is
			// available on this server, add this server
			// to the available_remotes array

			for(std::map<std::string, FileStatus*>::iterator
					it = m_files.upper_bound(m_name_bound);
					it != m_files.end(); ++it) {
				FileStatus *f = it->second;
				if (!f->received && sha1_set.count(f->sha1))
					f->available_remotes.push_back(remote_id);
			}
		}
		catch (SerializationError &e) {
			infostream << "Client: Remote server \""
				<< remote->baseurl << "\" sent invalid hash set: "
				<< e.what() << std::endl;
		}
	}
}

void ClientMediaDownloader::remoteMediaReceived(
		const HTTPFetchResult &fetch_result,
		Client *client)
{
	// Some remote server sent us a file.
	// -> decrement number of active fetches
	// -> mark file as received if fetch succeeded
	// -> try to load media

	std::string name;
	{
		std::unordered_map<unsigned long, std::string>::iterator it =
			m_remote_file_transfers.find(fetch_result.request_id);
		assert(it != m_remote_file_transfers.end());
		name = it->second;
		m_remote_file_transfers.erase(it);
	}

	sanity_check(m_files.count(name) != 0);

	FileStatus *filestatus = m_files[name];
	sanity_check(!filestatus->received);
	sanity_check(filestatus->current_remote >= 0);

	RemoteServerStatus *remote = m_remotes[filestatus->current_remote];

	filestatus->current_remote = -1;
	remote->active_count--;

	// If fetch succeeded, try to load media file

	if (fetch_result.succeeded) {
		bool success = checkAndLoad(name, filestatus->sha1,
				fetch_result.data, false, client);
		if (success) {
			filestatus->received = true;
			assert(m_uncached_received_count < m_uncached_count);
			m_uncached_received_count++;
		}
	}
}

s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus)
{
	// Pre-conditions
	assert(filestatus != NULL);
	assert(!filestatus->received);
	assert(filestatus->current_remote < 0);

	if (filestatus->available_remotes.empty())
		return -1;

	// Of all servers that claim to provide the file (and haven't
	// been unsuccessfully tried before), find the one with the
	// smallest number of currently active transfers

	s32 best = 0;
	s32 best_remote_id = filestatus->available_remotes[best];
	s32 best_active_count = m_remotes[best_remote_id]->active_count;

	for (u32 i = 1; i < filestatus->available_remotes.size(); ++i) {
		s32 remote_id = filestatus->available_remotes[i];
		s32 active_count = m_remotes[remote_id]->active_count;
		if (active_count < best_active_count) {
			best = i;
			best_remote_id = remote_id;
			best_active_count = active_count;
		}
	}

	filestatus->available_remotes.erase(
			filestatus->available_remotes.begin() + best);

	return best_remote_id;

}

void ClientMediaDownloader::startRemoteMediaTransfers()
{
	bool changing_name_bound = true;

	for (std::map<std::string, FileStatus*>::iterator
			files_iter = m_files.upper_bound(m_name_bound);
			files_iter != m_files.end(); ++files_iter) {

		// Abort if active fetch limit is exceeded
		if (m_httpfetch_active >= m_httpfetch_active_limit)
			break;