clientmedia.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. /*
  2. Minetest
  3. Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU Lesser General Public License as published by
  6. the Free Software Foundation; either version 2.1 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include "clientmedia.h"
  17. #include "httpfetch.h"
  18. #include "client.h"
  19. #include "filecache.h"
  20. #include "filesys.h"
  21. #include "log.h"
  22. #include "porting.h"
  23. #include "settings.h"
  24. #include "util/hex.h"
  25. #include "util/serialize.h"
  26. #include "util/sha1.h"
  27. #include "util/string.h"
  28. static std::string getMediaCacheDir()
  29. {
  30. return porting::path_cache + DIR_DELIM + "media";
  31. }
  32. bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata)
  33. {
  34. FileCache media_cache(getMediaCacheDir());
  35. std::string sha1_hex = hex_encode(raw_hash);
  36. if (!media_cache.exists(sha1_hex))
  37. return media_cache.update(sha1_hex, filedata);
  38. return true;
  39. }
  40. /*
  41. ClientMediaDownloader
  42. */
  43. ClientMediaDownloader::ClientMediaDownloader():
  44. m_httpfetch_caller(HTTPFETCH_DISCARD)
  45. {
  46. }
  47. ClientMediaDownloader::~ClientMediaDownloader()
  48. {
  49. if (m_httpfetch_caller != HTTPFETCH_DISCARD)
  50. httpfetch_caller_free(m_httpfetch_caller);
  51. for (auto &file_it : m_files)
  52. delete file_it.second;
  53. for (auto &remote : m_remotes)
  54. delete remote;
  55. }
  56. bool ClientMediaDownloader::loadMedia(Client *client, const std::string &data,
  57. const std::string &name)
  58. {
  59. return client->loadMedia(data, name);
  60. }
  61. void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1)
  62. {
  63. assert(!m_initial_step_done); // pre-condition
  64. // if name was already announced, ignore the new announcement
  65. if (m_files.count(name) != 0) {
  66. errorstream << "Client: ignoring duplicate media announcement "
  67. << "sent by server: \"" << name << "\""
  68. << std::endl;
  69. return;
  70. }
  71. // if name is empty or contains illegal characters, ignore the file
  72. if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) {
  73. errorstream << "Client: ignoring illegal file name "
  74. << "sent by server: \"" << name << "\""
  75. << std::endl;
  76. return;
  77. }
  78. // length of sha1 must be exactly 20 (160 bits), else ignore the file
  79. if (sha1.size() != 20) {
  80. errorstream << "Client: ignoring illegal SHA1 sent by server: "
  81. << hex_encode(sha1) << " \"" << name << "\""
  82. << std::endl;
  83. return;
  84. }
  85. FileStatus *filestatus = new FileStatus();
  86. filestatus->received = false;
  87. filestatus->sha1 = sha1;
  88. filestatus->current_remote = -1;
  89. m_files.insert(std::make_pair(name, filestatus));
  90. }
  91. void ClientMediaDownloader::addRemoteServer(const std::string &baseurl)
  92. {
  93. assert(!m_initial_step_done); // pre-condition
  94. #ifdef USE_CURL
  95. if (g_settings->getBool("enable_remote_media_server")) {
  96. infostream << "Client: Adding remote server \""
  97. << baseurl << "\" for media download" << std::endl;
  98. RemoteServerStatus *remote = new RemoteServerStatus();
  99. remote->baseurl = baseurl;
  100. remote->active_count = 0;
  101. m_remotes.push_back(remote);
  102. }
  103. #else
  104. infostream << "Client: Ignoring remote server \""
  105. << baseurl << "\" because cURL support is not compiled in"
  106. << std::endl;
  107. #endif
  108. }
  109. void ClientMediaDownloader::step(Client *client)
  110. {
  111. if (!m_initial_step_done) {
  112. initialStep(client);
  113. m_initial_step_done = true;
  114. }
  115. // Remote media: check for completion of fetches
  116. if (m_httpfetch_active) {
  117. bool fetched_something = false;
  118. HTTPFetchResult fetch_result;
  119. while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) {
  120. m_httpfetch_active--;
  121. fetched_something = true;
  122. // Is this a hashset (index.mth) or a media file?
  123. if (fetch_result.request_id < m_remotes.size())
  124. remoteHashSetReceived(fetch_result);
  125. else
  126. remoteMediaReceived(fetch_result, client);
  127. }
  128. if (fetched_something)
  129. startRemoteMediaTransfers();
  130. // Did all remote transfers end and no new ones can be started?
  131. // If so, request still missing files from the minetest server
  132. // (Or report that we have all files.)
  133. if (m_httpfetch_active == 0) {
  134. if (m_uncached_received_count < m_uncached_count) {
  135. infostream << "Client: Failed to remote-fetch "
  136. << (m_uncached_count-m_uncached_received_count)
  137. << " files. Requesting them"
  138. << " the usual way." << std::endl;
  139. }
  140. startConventionalTransfers(client);
  141. }
  142. }
  143. }
  144. void ClientMediaDownloader::initialStep(Client *client)
  145. {
  146. // Check media cache
  147. m_uncached_count = m_files.size();
  148. for (auto &file_it : m_files) {
  149. const std::string &name = file_it.first;
  150. FileStatus *filestatus = file_it.second;
  151. const std::string &sha1 = filestatus->sha1;
  152. if (tryLoadFromCache(name, sha1, client)) {
  153. filestatus->received = true;
  154. m_uncached_count--;
  155. }
  156. }
  157. assert(m_uncached_received_count == 0);
  158. // Create the media cache dir if we are likely to write to it
  159. if (m_uncached_count != 0)
  160. createCacheDirs();
  161. // If we found all files in the cache, report this fact to the server.
  162. // If the server reported no remote servers, immediately start
  163. // conventional transfers. Note: if cURL support is not compiled in,
  164. // m_remotes is always empty, so "!USE_CURL" is redundant but may
  165. // reduce the size of the compiled code
  166. if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) {
  167. startConventionalTransfers(client);
  168. }
  169. else {
  170. // Otherwise start off by requesting each server's sha1 set
  171. // This is the first time we use httpfetch, so alloc a caller ID
  172. m_httpfetch_caller = httpfetch_caller_alloc();
  173. // Set the active fetch limit to curl_parallel_limit or 84,
  174. // whichever is greater. This gives us some leeway so that
  175. // inefficiencies in communicating with the httpfetch thread
  176. // don't slow down fetches too much. (We still want some limit
  177. // so that when the first remote server returns its hash set,
  178. // not all files are requested from that server immediately.)
  179. // One such inefficiency is that ClientMediaDownloader::step()
  180. // is only called a couple times per second, while httpfetch
  181. // might return responses much faster than that.
  182. // Note that httpfetch strictly enforces curl_parallel_limit
  183. // but at no inter-thread communication cost. This however
  184. // doesn't help with the aforementioned inefficiencies.
  185. // The signifance of 84 is that it is 2*6*9 in base 13.
  186. m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit");
  187. m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84);
  188. // Write a list of hashes that we need. This will be POSTed
  189. // to the server using Content-Type: application/octet-stream
  190. std::string required_hash_set = serializeRequiredHashSet();
  191. // minor fixme: this loop ignores m_httpfetch_active_limit
  192. // another minor fixme, unlikely to matter in normal usage:
  193. // these index.mth fetches do (however) count against
  194. // m_httpfetch_active_limit when starting actual media file
  195. // requests, so if there are lots of remote servers that are
  196. // not responding, those will stall new media file transfers.
  197. for (u32 i = 0; i < m_remotes.size(); ++i) {
  198. assert(m_httpfetch_next_id == i);
  199. RemoteServerStatus *remote = m_remotes[i];
  200. actionstream << "Client: Contacting remote server \""
  201. << remote->baseurl << "\"" << std::endl;
  202. HTTPFetchRequest fetch_request;
  203. fetch_request.url =
  204. remote->baseurl + MTHASHSET_FILE_NAME;
  205. fetch_request.caller = m_httpfetch_caller;
  206. fetch_request.request_id = m_httpfetch_next_id; // == i
  207. fetch_request.method = HTTP_POST;
  208. fetch_request.raw_data = required_hash_set;
  209. fetch_request.extra_headers.emplace_back(
  210. "Content-Type: application/octet-stream");
  211. // Encapsulate possible IPv6 plain address in []
  212. std::string addr = client->getAddressName();
  213. if (addr.find(':', 0) != std::string::npos)
  214. addr = '[' + addr + ']';
  215. fetch_request.extra_headers.emplace_back(
  216. std::string("Referer: minetest://") +
  217. addr + ":" +
  218. std::to_string(client->getServerAddress().getPort()));
  219. httpfetch_async(fetch_request);
  220. m_httpfetch_active++;
  221. m_httpfetch_next_id++;
  222. m_outstanding_hash_sets++;
  223. }
  224. }
  225. }
  226. void ClientMediaDownloader::remoteHashSetReceived(
  227. const HTTPFetchResult &fetch_result)
  228. {
  229. u32 remote_id = fetch_result.request_id;
  230. assert(remote_id < m_remotes.size());
  231. RemoteServerStatus *remote = m_remotes[remote_id];
  232. m_outstanding_hash_sets--;
  233. if (fetch_result.succeeded) {
  234. try {
  235. // Server sent a list of file hashes that are
  236. // available on it, try to parse the list
  237. std::set<std::string> sha1_set;
  238. deSerializeHashSet(fetch_result.data, sha1_set);
  239. // Parsing succeeded: For every file that is
  240. // available on this server, add this server
  241. // to the available_remotes array
  242. for(auto it = m_files.upper_bound(m_name_bound);
  243. it != m_files.end(); ++it) {
  244. FileStatus *f = it->second;
  245. if (!f->received && sha1_set.count(f->sha1))
  246. f->available_remotes.push_back(remote_id);
  247. }
  248. }
  249. catch (SerializationError &e) {
  250. infostream << "Client: Remote server \""
  251. << remote->baseurl << "\" sent invalid hash set: "
  252. << e.what() << std::endl;
  253. }
  254. }
  255. }
  256. void ClientMediaDownloader::remoteMediaReceived(
  257. const HTTPFetchResult &fetch_result,
  258. Client *client)
  259. {
  260. // Some remote server sent us a file.
  261. // -> decrement number of active fetches
  262. // -> mark file as received if fetch succeeded
  263. // -> try to load media
  264. std::string name;
  265. {
  266. auto it = m_remote_file_transfers.find(fetch_result.request_id);
  267. assert(it != m_remote_file_transfers.end());
  268. name = it->second;
  269. m_remote_file_transfers.erase(it);
  270. }
  271. sanity_check(m_files.count(name) != 0);
  272. FileStatus *filestatus = m_files[name];
  273. sanity_check(!filestatus->received);
  274. sanity_check(filestatus->current_remote >= 0);
  275. RemoteServerStatus *remote = m_remotes[filestatus->current_remote];
  276. filestatus->current_remote = -1;
  277. remote->active_count--;
  278. // If fetch succeeded, try to load media file
  279. if (fetch_result.succeeded) {
  280. bool success = checkAndLoad(name, filestatus->sha1,
  281. fetch_result.data, false, client);
  282. if (success) {
  283. filestatus->received = true;
  284. assert(m_uncached_received_count < m_uncached_count);
  285. m_uncached_received_count++;
  286. }
  287. }
  288. }
  289. s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus)
  290. {
  291. // Pre-conditions
  292. assert(filestatus != NULL);
  293. assert(!filestatus->received);
  294. assert(filestatus->current_remote < 0);
  295. if (filestatus->available_remotes.empty())
  296. return -1;
  297. // Of all servers that claim to provide the file (and haven't
  298. // been unsuccessfully tried before), find the one with the
  299. // smallest number of currently active transfers
  300. s32 best = 0;
  301. s32 best_remote_id = filestatus->available_remotes[best];
  302. s32 best_active_count = m_remotes[best_remote_id]->active_count;
  303. for (u32 i = 1; i < filestatus->available_remotes.size(); ++i) {
  304. s32 remote_id = filestatus->available_remotes[i];
  305. s32 active_count = m_remotes[remote_id]->active_count;
  306. if (active_count < best_active_count) {
  307. best = i;
  308. best_remote_id = remote_id;
  309. best_active_count = active_count;
  310. }
  311. }
  312. filestatus->available_remotes.erase(
  313. filestatus->available_remotes.begin() + best);
  314. return best_remote_id;
  315. }
  316. void ClientMediaDownloader::startRemoteMediaTransfers()
  317. {
  318. bool changing_name_bound = true;
  319. for (auto files_iter = m_files.upper_bound(m_name_bound);
  320. files_iter != m_files.end(); ++files_iter) {
  321. // Abort if active fetch limit is exceeded
  322. if (m_httpfetch_active >= m_httpfetch_active_limit)
  323. break;
  324. const std::string &name = files_iter->first;
  325. FileStatus *filestatus = files_iter->second;
  326. if (!filestatus->received && filestatus->current_remote < 0) {
  327. // File has not been received yet and is not currently
  328. // being transferred. Choose a server for it.
  329. s32 remote_id = selectRemoteServer(filestatus);
  330. if (remote_id >= 0) {
  331. // Found a server, so start fetching
  332. RemoteServerStatus *remote =
  333. m_remotes[remote_id];
  334. std::string url = remote->baseurl +
  335. hex_encode(filestatus->sha1);
  336. verbosestream << "Client: "
  337. << "Requesting remote media file "
  338. << "\"" << name << "\" "
  339. << "\"" << url << "\"" << std::endl;
  340. HTTPFetchRequest fetch_request;
  341. fetch_request.url = url;
  342. fetch_request.caller = m_httpfetch_caller;
  343. fetch_request.request_id = m_httpfetch_next_id;
  344. fetch_request.timeout =
  345. g_settings->getS32("curl_file_download_timeout");
  346. httpfetch_async(fetch_request);
  347. m_remote_file_transfers.insert(std::make_pair(
  348. m_httpfetch_next_id,
  349. name));
  350. filestatus->current_remote = remote_id;
  351. remote->active_count++;
  352. m_httpfetch_active++;
  353. m_httpfetch_next_id++;
  354. }
  355. }
  356. if (filestatus->received ||
  357. (filestatus->current_remote < 0 &&
  358. !m_outstanding_hash_sets)) {
  359. // If we arrive here, we conclusively know that we
  360. // won't fetch this file from a remote server in the
  361. // future. So update the name bound if possible.
  362. if (changing_name_bound)
  363. m_name_bound = name;
  364. }
  365. else
  366. changing_name_bound = false;
  367. }
  368. }
  369. void ClientMediaDownloader::startConventionalTransfers(Client *client)
  370. {
  371. assert(m_httpfetch_active == 0); // pre-condition
  372. if (m_uncached_received_count != m_uncached_count) {
  373. // Some media files have not been received yet, use the
  374. // conventional slow method (minetest protocol) to get them
  375. std::vector<std::string> file_requests;
  376. for (auto &file : m_files) {
  377. if (!file.second->received)
  378. file_requests.push_back(file.first);
  379. }
  380. assert((s32) file_requests.size() ==
  381. m_uncached_count - m_uncached_received_count);
  382. client->request_media(file_requests);
  383. }
  384. }
  385. bool ClientMediaDownloader::conventionalTransferDone(
  386. const std::string &name,
  387. const std::string &data,
  388. Client *client)
  389. {
  390. // Check that file was announced
  391. auto file_iter = m_files.find(name);
  392. if (file_iter == m_files.end()) {
  393. errorstream << "Client: server sent media file that was"
  394. << "not announced, ignoring it: \"" << name << "\""
  395. << std::endl;
  396. return false;
  397. }
  398. FileStatus *filestatus = file_iter->second;
  399. assert(filestatus != NULL);
  400. // Check that file hasn't already been received
  401. if (filestatus->received) {
  402. errorstream << "Client: server sent media file that we already"
  403. << "received, ignoring it: \"" << name << "\""
  404. << std::endl;
  405. return true;
  406. }
  407. // Mark file as received, regardless of whether loading it works and
  408. // whether the checksum matches (because at this point there is no
  409. // other server that could send a replacement)
  410. filestatus->received = true;
  411. assert(m_uncached_received_count < m_uncached_count);
  412. m_uncached_received_count++;
  413. // Check that received file matches announced checksum
  414. // If so, load it
  415. checkAndLoad(name, filestatus->sha1, data, false, client);
  416. return true;
  417. }
  418. /*
  419. IClientMediaDownloader
  420. */
  421. IClientMediaDownloader::IClientMediaDownloader():
  422. m_media_cache(getMediaCacheDir()), m_write_to_cache(true)
  423. {
  424. }
  425. void IClientMediaDownloader::createCacheDirs()
  426. {
  427. if (!m_write_to_cache)
  428. return;
  429. std::string path = getMediaCacheDir();
  430. if (!fs::CreateAllDirs(path)) {
  431. errorstream << "Client: Could not create media cache directory: "
  432. << path << std::endl;
  433. }
  434. }
  435. bool IClientMediaDownloader::tryLoadFromCache(const std::string &name,
  436. const std::string &sha1, Client *client)
  437. {
  438. std::ostringstream tmp_os(std::ios_base::binary);
  439. bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os);
  440. // If found in cache, try to load it from there
  441. if (found_in_cache)
  442. return checkAndLoad(name, sha1, tmp_os.str(), true, client);
  443. return false;
  444. }
  445. bool IClientMediaDownloader::checkAndLoad(
  446. const std::string &name, const std::string &sha1,
  447. const std::string &data, bool is_from_cache, Client *client)
  448. {
  449. const char *cached_or_received = is_from_cache ? "cached" : "received";
  450. const char *cached_or_received_uc = is_from_cache ? "Cached" : "Received";
  451. std::string sha1_hex = hex_encode(sha1);
  452. // Compute actual checksum of data
  453. std::string data_sha1;
  454. {
  455. SHA1 data_sha1_calculator;
  456. data_sha1_calculator.addBytes(data.c_str(), data.size());
  457. unsigned char *data_tmpdigest = data_sha1_calculator.getDigest();
  458. data_sha1.assign((char*) data_tmpdigest, 20);
  459. free(data_tmpdigest);
  460. }
  461. // Check that received file matches announced checksum
  462. if (data_sha1 != sha1) {
  463. std::string data_sha1_hex = hex_encode(data_sha1);
  464. infostream << "Client: "
  465. << cached_or_received_uc << " media file "
  466. << sha1_hex << " \"" << name << "\" "
  467. << "mismatches actual checksum " << data_sha1_hex
  468. << std::endl;
  469. return false;
  470. }
  471. // Checksum is ok, try loading the file
  472. bool success = loadMedia(client, data, name);
  473. if (!success) {
  474. infostream << "Client: "
  475. << "Failed to load " << cached_or_received << " media: "
  476. << sha1_hex << " \"" << name << "\""
  477. << std::endl;
  478. return false;
  479. }
  480. verbosestream << "Client: "
  481. << "Loaded " << cached_or_received << " media: "
  482. << sha1_hex << " \"" << name << "\""
  483. << std::endl;
  484. // Update cache (unless we just loaded the file from the cache)
  485. if (!is_from_cache && m_write_to_cache)
  486. m_media_cache.update(sha1_hex, data);
  487. return true;
  488. }
  489. /*
  490. Minetest Hashset File Format
  491. All values are stored in big-endian byte order.
  492. [u32] signature: 'MTHS'
  493. [u16] version: 1
  494. For each hash in set:
  495. [u8*20] SHA1 hash
  496. Version changes:
  497. 1 - Initial version
  498. */
  499. std::string ClientMediaDownloader::serializeRequiredHashSet()
  500. {
  501. std::ostringstream os(std::ios::binary);
  502. writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature
  503. writeU16(os, 1); // version
  504. // Write list of hashes of files that have not been
  505. // received (found in cache) yet
  506. for (const auto &it : m_files) {
  507. if (!it.second->received) {
  508. FATAL_ERROR_IF(it.second->sha1.size() != 20, "Invalid SHA1 size");
  509. os << it.second->sha1;
  510. }
  511. }
  512. return os.str();
  513. }
  514. void ClientMediaDownloader::deSerializeHashSet(const std::string &data,
  515. std::set<std::string> &result)
  516. {
  517. if (data.size() < 6 || data.size() % 20 != 6) {
  518. throw SerializationError(
  519. "ClientMediaDownloader::deSerializeHashSet: "
  520. "invalid hash set file size");
  521. }
  522. const u8 *data_cstr = (const u8*) data.c_str();
  523. u32 signature = readU32(&data_cstr[0]);
  524. if (signature != MTHASHSET_FILE_SIGNATURE) {
  525. throw SerializationError(
  526. "ClientMediaDownloader::deSerializeHashSet: "
  527. "invalid hash set file signature");
  528. }
  529. u16 version = readU16(&data_cstr[4]);
  530. if (version != 1) {
  531. throw SerializationError(
  532. "ClientMediaDownloader::deSerializeHashSet: "
  533. "unsupported hash set file version");
  534. }
  535. for (u32 pos = 6; pos < data.size(); pos += 20) {
  536. result.insert(data.substr(pos, 20));
  537. }
  538. }
  539. /*
  540. SingleMediaDownloader
  541. */
  542. SingleMediaDownloader::SingleMediaDownloader(bool write_to_cache):
  543. m_httpfetch_caller(HTTPFETCH_DISCARD)
  544. {
  545. m_write_to_cache = write_to_cache;
  546. }
  547. SingleMediaDownloader::~SingleMediaDownloader()
  548. {
  549. if (m_httpfetch_caller != HTTPFETCH_DISCARD)
  550. httpfetch_caller_free(m_httpfetch_caller);
  551. }
  552. bool SingleMediaDownloader::loadMedia(Client *client, const std::string &data,
  553. const std::string &name)
  554. {
  555. return client->loadMedia(data, name, true);
  556. }
  557. void SingleMediaDownloader::addFile(const std::string &name, const std::string &sha1)
  558. {
  559. assert(m_stage == STAGE_INIT); // pre-condition
  560. assert(!name.empty());
  561. assert(sha1.size() == 20);
  562. FATAL_ERROR_IF(!m_file_name.empty(), "Cannot add a second file");
  563. m_file_name = name;
  564. m_file_sha1 = sha1;
  565. }
  566. void SingleMediaDownloader::addRemoteServer(const std::string &baseurl)
  567. {
  568. assert(m_stage == STAGE_INIT); // pre-condition
  569. if (g_settings->getBool("enable_remote_media_server"))
  570. m_remotes.emplace_back(baseurl);
  571. }
  572. void SingleMediaDownloader::step(Client *client)
  573. {
  574. if (m_stage == STAGE_INIT) {
  575. m_stage = STAGE_CACHE_CHECKED;
  576. initialStep(client);
  577. }
  578. // Remote media: check for completion of fetches
  579. if (m_httpfetch_caller != HTTPFETCH_DISCARD) {
  580. HTTPFetchResult fetch_result;
  581. while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) {
  582. remoteMediaReceived(fetch_result, client);
  583. }
  584. }
  585. }
  586. bool SingleMediaDownloader::conventionalTransferDone(const std::string &name,
  587. const std::string &data, Client *client)
  588. {
  589. if (name != m_file_name)
  590. return false;
  591. // Mark file as received unconditionally and try to load it
  592. m_stage = STAGE_DONE;
  593. checkAndLoad(name, m_file_sha1, data, false, client);
  594. return true;
  595. }
  596. void SingleMediaDownloader::initialStep(Client *client)
  597. {
  598. if (tryLoadFromCache(m_file_name, m_file_sha1, client))
  599. m_stage = STAGE_DONE;
  600. if (isDone())
  601. return;
  602. createCacheDirs();
  603. // If the server reported no remote servers, immediately fall back to
  604. // conventional transfer.
  605. if (!USE_CURL || m_remotes.empty()) {
  606. startConventionalTransfer(client);
  607. } else {
  608. // Otherwise start by requesting the file from the first remote media server
  609. m_httpfetch_caller = httpfetch_caller_alloc();
  610. m_current_remote = 0;
  611. startRemoteMediaTransfer();
  612. }
  613. }
  614. void SingleMediaDownloader::remoteMediaReceived(
  615. const HTTPFetchResult &fetch_result, Client *client)
  616. {
  617. sanity_check(!isDone());
  618. sanity_check(m_current_remote >= 0);
  619. // If fetch succeeded, try to load it
  620. if (fetch_result.succeeded) {
  621. bool success = checkAndLoad(m_file_name, m_file_sha1,
  622. fetch_result.data, false, client);
  623. if (success) {
  624. m_stage = STAGE_DONE;
  625. return;
  626. }
  627. }
  628. // Otherwise try the next remote server or fall back to conventional transfer
  629. m_current_remote++;
  630. if (m_current_remote >= (int)m_remotes.size()) {
  631. infostream << "Client: Failed to remote-fetch \"" << m_file_name
  632. << "\". Requesting it the usual way." << std::endl;
  633. m_current_remote = -1;
  634. startConventionalTransfer(client);
  635. } else {
  636. startRemoteMediaTransfer();
  637. }
  638. }
  639. void SingleMediaDownloader::startRemoteMediaTransfer()
  640. {
  641. std::string url = m_remotes.at(m_current_remote) + hex_encode(m_file_sha1);
  642. verbosestream << "Client: Requesting remote media file "
  643. << "\"" << m_file_name << "\" " << "\"" << url << "\"" << std::endl;
  644. HTTPFetchRequest fetch_request;
  645. fetch_request.url = url;
  646. fetch_request.caller = m_httpfetch_caller;
  647. fetch_request.request_id = m_httpfetch_next_id;
  648. fetch_request.timeout = g_settings->getS32("curl_file_download_timeout");
  649. httpfetch_async(fetch_request);
  650. m_httpfetch_next_id++;
  651. }
  652. void SingleMediaDownloader::startConventionalTransfer(Client *client)
  653. {
  654. std::vector<std::string> requests;
  655. requests.emplace_back(m_file_name);
  656. client->request_media(requests);
  657. }