OpenTTD Source 15.0-beta2
network_content.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD 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.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "../stdafx.h"
11#include "../rev.h"
12#include "../ai/ai.hpp"
13#include "../game/game.hpp"
14#include "../window_func.h"
15#include "../error.h"
16#include "../fileio_func.h"
17#include "../base_media_base.h"
18#include "../base_media_graphics.h"
19#include "../base_media_music.h"
20#include "../base_media_sounds.h"
21#include "../settings_type.h"
22#include "../strings_func.h"
23#include "../timer/timer.h"
24#include "../timer/timer_window.h"
25#include "network_content.h"
26
27#include "table/strings.h"
28
29#if defined(WITH_ZLIB)
30# include <zlib.h>
31# if defined(_WIN32)
32 /* Required for: dup, fileno, close */
33# include <io.h>
34# endif
35#endif
36
37#ifdef __EMSCRIPTEN__
38# include <emscripten.h>
39#endif
40
41#include "../safeguards.h"
42
43extern bool HasScenario(const ContentInfo &ci, bool md5sum);
44
47
49static bool HasGRFConfig(const ContentInfo &ci, bool md5sum)
50{
51 return FindGRFConfig(std::byteswap(ci.unique_id), md5sum ? FGCM_EXACT : FGCM_ANY, md5sum ? &ci.md5sum : nullptr) != nullptr;
52}
53
61using HasContentProc = bool(const ContentInfo &ci, bool md5sum);
62
69{
70 switch (type) {
75 case CONTENT_TYPE_AI: return AI::HasAI;
76 case CONTENT_TYPE_AI_LIBRARY: return AI::HasAILibrary;
78 case CONTENT_TYPE_GAME_LIBRARY: return Game::HasGameLibrary;
81 default: return nullptr;
82 }
83}
84
86{
87 auto ci = std::make_unique<ContentInfo>();
88 ci->type = (ContentType)p.Recv_uint8();
89 ci->id = (ContentID)p.Recv_uint32();
90 ci->filesize = p.Recv_uint32();
91
96
97 ci->unique_id = p.Recv_uint32();
98 p.Recv_bytes(ci->md5sum);
99
100 uint dependency_count = p.Recv_uint8();
101 ci->dependencies.reserve(dependency_count);
102 for (uint i = 0; i < dependency_count; i++) {
103 ContentID dependency_cid = (ContentID)p.Recv_uint32();
104 ci->dependencies.push_back(dependency_cid);
105 this->reverse_dependency_map.emplace(dependency_cid, ci->id);
106 }
107
108 uint tag_count = p.Recv_uint8();
109 ci->tags.reserve(tag_count);
110 for (uint i = 0; i < tag_count; i++) ci->tags.push_back(p.Recv_string(NETWORK_CONTENT_TAG_LENGTH));
111
112 if (!ci->IsValid()) {
113 this->CloseConnection();
114 return false;
115 }
116
118 if (proc != nullptr) {
119 if (proc(*ci, true)) {
120 ci->state = ContentInfo::ALREADY_HERE;
121 } else {
122 ci->state = ContentInfo::UNSELECTED;
123 if (proc(*ci, false)) ci->upgrade = true;
124 }
125 } else {
126 ci->state = ContentInfo::UNSELECTED;
127 }
128
129 /* Something we don't have and has filesize 0 does not exist in the system */
130 if (ci->state == ContentInfo::UNSELECTED && ci->filesize == 0) ci->state = ContentInfo::DOES_NOT_EXIST;
131
132 /* Do we already have a stub for this? */
133 for (const auto &ici : this->infos) {
134 if (ici->type == ci->type && ici->unique_id == ci->unique_id && ci->md5sum == ici->md5sum) {
135 /* Preserve the name if possible */
136 if (ci->name.empty()) ci->name = ici->name;
137 if (ici->IsSelected()) ci->state = ici->state;
138
139 /*
140 * As ici might be selected by the content window we cannot delete that.
141 * However, we want to keep most of the values of ci, except the values
142 * we (just) already preserved.
143 */
144 *ici = *ci;
145
146 this->OnReceiveContentInfo(*ici);
147 return true;
148 }
149 }
150
151 /* Missing content info? Don't list it */
152 if (ci->filesize == 0) {
153 return true;
154 }
155
156 ContentInfo *info = this->infos.emplace_back(std::move(ci)).get();
157
158 /* Incoming data means that we might need to reconsider dependencies */
159 ConstContentVector parents;
160 this->ReverseLookupTreeDependency(parents, info);
161 for (const ContentInfo *ici : parents) {
162 this->CheckDependencyState(*ici);
163 }
164
165 this->OnReceiveContentInfo(*info);
166
167 return true;
168}
169
175{
176 if (type == CONTENT_TYPE_END) {
187 return;
188 }
189
190 this->Connect();
191
192 auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_INFO_LIST);
193 p->Send_uint8 ((uint8_t)type);
194 p->Send_uint32(0xffffffff);
195 p->Send_uint8 (1);
196 p->Send_string("vanilla");
197 p->Send_string(_openttd_content_version);
198
199 /* Patchpacks can extend the list with one. In BaNaNaS metadata you can
200 * add a branch in the 'compatibility' list, to filter on this. If you want
201 * your patchpack to be mentioned in the BaNaNaS web-interface, create an
202 * issue on https://github.com/OpenTTD/bananas-api asking for this.
203
204 p->Send_string("patchpack"); // Or what-ever the name of your patchpack is.
205 p->Send_string(_openttd_content_version_patchpack);
206
207 */
208
209 this->SendPacket(std::move(p));
210}
211
217void ClientNetworkContentSocketHandler::RequestContentList(std::span<const ContentID> content_ids)
218{
219 /* We can "only" send a limited number of IDs in a single packet.
220 * A packet begins with the packet size and a byte for the type.
221 * Then this packet adds a uint16_t for the count in this packet.
222 * The rest of the packet can be used for the IDs. */
223 static constexpr size_t MAX_CONTENT_IDS_PER_PACKET = (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint16_t)) / sizeof(uint32_t);
224
225 if (content_ids.empty()) return;
226
227 this->Connect();
228
229 for (auto it = std::begin(content_ids); it != std::end(content_ids); /* nothing */) {
230 auto last = std::ranges::next(it, MAX_CONTENT_IDS_PER_PACKET, std::end(content_ids));
231
232 auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_INFO_ID, TCP_MTU);
233 p->Send_uint16(std::distance(it, last));
234
235 for (; it != last; ++it) {
236 p->Send_uint32(*it);
237 }
238
239 this->SendPacket(std::move(p));
240 }
241}
242
249{
250 if (cv == nullptr) return;
251
252 this->Connect();
253
254 assert(cv->size() < 255);
255 assert(cv->size() < (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint8_t)) /
256 (sizeof(uint8_t) + sizeof(uint32_t) + (send_md5sum ? MD5_HASH_BYTES : 0)));
257
258 auto p = std::make_unique<Packet>(this, send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID, TCP_MTU);
259 p->Send_uint8((uint8_t)cv->size());
260
261 for (const auto &ci : *cv) {
262 p->Send_uint8((uint8_t)ci->type);
263 p->Send_uint32(ci->unique_id);
264 if (!send_md5sum) continue;
265 p->Send_bytes(ci->md5sum);
266 }
267
268 this->SendPacket(std::move(p));
269
270 for (auto &ci : *cv) {
271 bool found = false;
272 for (const auto &ci2 : this->infos) {
273 if (ci->type == ci2->type && ci->unique_id == ci2->unique_id &&
274 (!send_md5sum || ci->md5sum == ci2->md5sum)) {
275 found = true;
276 break;
277 }
278 }
279 if (!found) {
280 this->infos.push_back(std::move(ci));
281 }
282 }
283}
284
291void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes, bool fallback)
292{
293 bytes = 0;
294
295 ContentIDList content;
296 for (const auto &ci : this->infos) {
297 if (!ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) continue;
298
299 content.push_back(ci->id);
300 bytes += ci->filesize;
301 }
302
303 files = (uint)content.size();
304
305 /* If there's nothing to download, do nothing. */
306 if (files == 0) return;
307
308 this->is_cancelled = false;
309
310 if (fallback) {
311 this->DownloadSelectedContentFallback(content);
312 } else {
313 this->DownloadSelectedContentHTTP(content);
314 }
315}
316
322{
323 std::string content_request;
324 for (const ContentID &id : content) {
325 content_request += std::to_string(id) + "\n";
326 }
327
328 this->http_response_index = -1;
329
330 NetworkHTTPSocketHandler::Connect(NetworkContentMirrorUriString(), this, std::move(content_request));
331}
332
338{
339 uint count = (uint)content.size();
340 const ContentID *content_ids = content.data();
341 this->Connect();
342
343 while (count > 0) {
344 /* We can "only" send a limited number of IDs in a single packet.
345 * A packet begins with the packet size and a byte for the type.
346 * Then this packet adds a uint16_t for the count in this packet.
347 * The rest of the packet can be used for the IDs. */
348 uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint16_t)) / sizeof(uint32_t));
349
350 auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_CONTENT, TCP_MTU);
351 p->Send_uint16(p_count);
352
353 for (uint i = 0; i < p_count; i++) {
354 p->Send_uint32(content_ids[i]);
355 }
356
357 this->SendPacket(std::move(p));
358 count -= p_count;
359 content_ids += p_count;
360 }
361}
362
370static std::string GetFullFilename(const ContentInfo &ci, bool compressed)
371{
373 if (dir == NO_DIRECTORY) return {};
374
375 std::string buf = FioGetDirectory(SP_AUTODOWNLOAD_DIR, dir);
376 buf += ci.filename;
377 buf += compressed ? ".tar.gz" : ".tar";
378
379 return buf;
380}
381
387static bool GunzipFile(const ContentInfo &ci)
388{
389#if defined(WITH_ZLIB)
390 bool ret = true;
391
392 /* Need to open the file with fopen() to support non-ASCII on Windows. */
393 auto ftmp = FileHandle::Open(GetFullFilename(ci, true), "rb");
394 if (!ftmp.has_value()) return false;
395 /* Duplicate the handle, and close the FILE*, to avoid double-closing the handle later. */
396 int fdup = dup(fileno(*ftmp));
397 gzFile fin = gzdopen(fdup, "rb");
398 ftmp.reset();
399
400 auto fout = FileHandle::Open(GetFullFilename(ci, false), "wb");
401
402 if (fin == nullptr || !fout.has_value()) {
403 ret = false;
404 } else {
405 uint8_t buff[8192];
406 for (;;) {
407 int read = gzread(fin, buff, sizeof(buff));
408 if (read == 0) {
409 /* If gzread() returns 0, either the end-of-file has been
410 * reached or an underlying read error has occurred.
411 *
412 * gzeof() can't be used, because:
413 * 1.2.5 - it is safe, 1 means 'everything was OK'
414 * 1.2.3.5, 1.2.4 - 0 or 1 is returned 'randomly'
415 * 1.2.3.3 - 1 is returned for truncated archive
416 *
417 * So we use gzerror(). When proper end of archive
418 * has been reached, then:
419 * errnum == Z_STREAM_END in 1.2.3.3,
420 * errnum == 0 in 1.2.4 and 1.2.5 */
421 int errnum;
422 gzerror(fin, &errnum);
423 if (errnum != 0 && errnum != Z_STREAM_END) ret = false;
424 break;
425 }
426 if (read < 0 || static_cast<size_t>(read) != fwrite(buff, 1, read, *fout)) {
427 /* If gzread() returns -1, there was an error in archive */
428 ret = false;
429 break;
430 }
431 /* DO NOT DO THIS! It will fail to detect broken archive with 1.2.3.3!
432 * if (read < sizeof(buff)) break; */
433 }
434 }
435
436 if (fin != nullptr) {
437 gzclose(fin);
438 } else if (fdup != -1) {
439 /* Failing gzdopen does not close the passed file descriptor. */
440 close(fdup);
441 }
442
443 return ret;
444#else
445 NOT_REACHED();
446#endif /* defined(WITH_ZLIB) */
447}
448
456static inline ssize_t TransferOutFWrite(std::optional<FileHandle> &file, const char *buffer, size_t amount)
457{
458 return fwrite(buffer, 1, amount, *file);
459}
460
462{
463 if (!this->cur_file.has_value()) {
464 /* When we haven't opened a file this must be our first packet with metadata. */
465 this->cur_info = std::make_unique<ContentInfo>();
466 this->cur_info->type = (ContentType)p.Recv_uint8();
467 this->cur_info->id = (ContentID)p.Recv_uint32();
468 this->cur_info->filesize = p.Recv_uint32();
470
471 if (!this->BeforeDownload()) {
472 this->CloseConnection();
473 return false;
474 }
475 } else {
476 /* We have a file opened, thus are downloading internal content */
477 size_t to_read = p.RemainingBytesToTransfer();
478 if (to_read != 0 && static_cast<size_t>(p.TransferOut(TransferOutFWrite, std::ref(this->cur_file))) != to_read) {
481 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD),
482 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE),
483 WL_ERROR);
484 this->CloseConnection();
485 this->cur_file.reset();
486
487 return false;
488 }
489
490 this->OnDownloadProgress(*this->cur_info, (int)to_read);
491
492 if (to_read == 0) this->AfterDownload();
493 }
494
495 return true;
496}
497
503{
504 if (!this->cur_info->IsValid()) {
505 this->cur_info.reset();
506 return false;
507 }
508
509 if (this->cur_info->filesize != 0) {
510 /* The filesize is > 0, so we are going to download it */
511 std::string filename = GetFullFilename(*this->cur_info, true);
512 if (filename.empty() || !(this->cur_file = FileHandle::Open(filename, "wb")).has_value()) {
513 /* Unless that fails of course... */
516 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD),
517 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE),
518 WL_ERROR);
519 return false;
520 }
521 }
522 return true;
523}
524
530{
531 /* We read nothing; that's our marker for end-of-stream.
532 * Now gunzip the tar and make it known. */
533 this->cur_file.reset();
534
535 if (GunzipFile(*this->cur_info)) {
536 FioRemove(GetFullFilename(*this->cur_info, true));
537
539 if (sd == NO_DIRECTORY) NOT_REACHED();
540
541 TarScanner ts;
542 std::string fname = GetFullFilename(*this->cur_info, false);
543 ts.AddFile(sd, fname);
544
545 if (this->cur_info->type == CONTENT_TYPE_BASE_MUSIC) {
546 /* Music can't be in a tar. So extract the tar! */
547 ExtractTar(fname, BASESET_DIR);
548 FioRemove(fname);
549 }
550
551#ifdef __EMSCRIPTEN__
552 EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
553#endif
554
555 this->OnDownloadComplete(this->cur_info->id);
556 } else {
557 ShowErrorMessage(GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_EXTRACT), {}, WL_ERROR);
558 }
559}
560
562{
563 return this->is_cancelled;
564}
565
566/* Also called to just clean up the mess. */
568{
569 this->http_response.clear();
570 this->http_response.shrink_to_fit();
571 this->http_response_index = -2;
572
573 if (this->cur_file.has_value()) {
574 this->OnDownloadProgress(*this->cur_info, -1);
575
576 this->cur_file.reset();
577 }
578
579 /* If we fail, download the rest via the 'old' system. */
580 if (!this->is_cancelled) {
581 uint files, bytes;
582
583 this->DownloadSelectedContent(files, bytes, true);
584 }
585}
586
587void ClientNetworkContentSocketHandler::OnReceiveData(std::unique_ptr<char[]> data, size_t length)
588{
589 assert(data.get() == nullptr || length != 0);
590
591 /* Ignore any latent data coming from a connection we closed. */
592 if (this->http_response_index == -2) {
593 return;
594 }
595
596 if (this->http_response_index == -1) {
597 if (data != nullptr) {
598 /* Append the rest of the response. */
599 this->http_response.insert(this->http_response.end(), data.get(), data.get() + length);
600 return;
601 } else {
602 /* Make sure the response is properly terminated. */
603 this->http_response.push_back('\0');
604
605 /* And prepare for receiving the rest of the data. */
606 this->http_response_index = 0;
607 }
608 }
609
610 if (data != nullptr) {
611 /* We have data, so write it to the file. */
612 if (fwrite(data.get(), 1, length, *this->cur_file) != length) {
613 /* Writing failed somehow, let try via the old method. */
614 this->OnFailure();
615 } else {
616 /* Just received the data. */
617 this->OnDownloadProgress(*this->cur_info, (int)length);
618 }
619
620 /* Nothing more to do now. */
621 return;
622 }
623
624 if (this->cur_file.has_value()) {
625 /* We've finished downloading a file. */
626 this->AfterDownload();
627 }
628
629 if ((uint)this->http_response_index >= this->http_response.size()) {
630 /* It's not a real failure, but if there's
631 * nothing more to download it helps with
632 * cleaning up the stuff we allocated. */
633 this->OnFailure();
634 return;
635 }
636
637 /* When we haven't opened a file this must be our first packet with metadata. */
638 this->cur_info = std::make_unique<ContentInfo>();
639
641#define check_not_null(p) { if ((p) == nullptr) { this->OnFailure(); return; } }
643#define check_and_terminate(p) { check_not_null(p); *(p) = '\0'; }
644
645 for (;;) {
646 char *str = this->http_response.data() + this->http_response_index;
647 char *p = strchr(str, '\n');
648 check_and_terminate(p);
649
650 /* Update the index for the next one */
651 this->http_response_index += (int)strlen(str) + 1;
652
653 /* Read the ID */
654 p = strchr(str, ',');
655 check_and_terminate(p);
656 this->cur_info->id = (ContentID)atoi(str);
657
658 /* Read the type */
659 str = p + 1;
660 p = strchr(str, ',');
661 check_and_terminate(p);
662 this->cur_info->type = (ContentType)atoi(str);
663
664 /* Read the file size */
665 str = p + 1;
666 p = strchr(str, ',');
667 check_and_terminate(p);
668 this->cur_info->filesize = atoi(str);
669
670 /* Read the URL */
671 str = p + 1;
672 /* Is it a fallback URL? If so, just continue with the next one. */
673 if (strncmp(str, "ottd", 4) == 0) {
674 if ((uint)this->http_response_index >= this->http_response.size()) {
675 /* Have we gone through all lines? */
676 this->OnFailure();
677 return;
678 }
679 continue;
680 }
681
682 p = strrchr(str, '/');
683 check_not_null(p);
684 p++; // Start after the '/'
685
686 std::string filename = p;
687 /* Remove the extension from the string. */
688 for (uint i = 0; i < 2; i++) {
689 auto pos = filename.find_last_of('.');
690 if (pos == std::string::npos) {
691 this->OnFailure();
692 return;
693 }
694 filename.erase(pos);
695 }
696
697 /* Copy the string, without extension, to the filename. */
698 this->cur_info->filename = std::move(filename);
699
700 /* Request the next file. */
701 if (!this->BeforeDownload()) {
702 this->OnFailure();
703 return;
704 }
705
707 return;
708 }
709
710#undef check
711#undef check_and_terminate
712}
713
716public:
722
723 void OnFailure() override
724 {
727 }
728
729 void OnConnect(SOCKET s) override
730 {
731 assert(_network_content_client.sock == INVALID_SOCKET);
732 _network_content_client.last_activity = std::chrono::steady_clock::now();
737 }
738};
739
744{
745 if (this->sock != INVALID_SOCKET || this->is_connecting) return;
746
747 this->is_cancelled = false;
748 this->is_connecting = true;
749
750 TCPConnecter::Create<NetworkContentConnecter>(NetworkContentServerConnectionString());
751}
752
767
772{
773 this->is_cancelled = true;
774 this->CloseConnection();
775}
776
782{
783 if (this->sock == INVALID_SOCKET || this->is_connecting) return;
784
785 /* Close the connection to the content server after inactivity; there can still be downloads pending via HTTP. */
786 if (std::chrono::steady_clock::now() > this->last_activity + IDLE_TIMEOUT) {
787 this->CloseConnection();
788 return;
789 }
790
791 if (this->CanSendReceive()) {
792 if (this->ReceivePackets()) {
793 /* Only update activity once a packet is received, instead of every time we try it. */
794 this->last_activity = std::chrono::steady_clock::now();
795 }
796 }
797
798 this->SendPackets();
799}
800
802static constexpr auto CONTENT_QUEUE_TIMEOUT = std::chrono::milliseconds(100);
803
804static TimeoutTimer<TimerWindow> _request_queue_timeout = {CONTENT_QUEUE_TIMEOUT, []() {
806}};
807
813{
814 /* When we tried to download it already, don't try again */
815 if (std::ranges::find(this->requested, cid) != this->requested.end()) return;
816
817 this->requested.push_back(cid);
818 this->queued.push_back(cid);
819 _request_queue_timeout.Reset();
820}
821
826{
827 if (this->queued.empty()) return;
828
829 /* Wait until we've briefly stopped receiving data (which will contain more content) before making the request. */
830 if (std::chrono::steady_clock::now() <= this->last_activity + CONTENT_QUEUE_TIMEOUT) {
831 _request_queue_timeout.Reset();
832 return;
833 }
834
835 /* Move the queue locally so more ids can be queued for later. */
836 ContentIDList queue;
837 queue.swap(this->queued);
838
839 /* Remove ids that have since been received since the request was queued. */
840 queue.erase(std::remove_if(std::begin(queue), std::end(queue), [this](ContentID content_id) {
841 return std::ranges::find(this->infos, content_id, &ContentInfo::id) != std::end(this->infos);
842 }), std::end(queue));
843
844 this->RequestContentList(queue);
845}
846
853{
854 for (const auto &ci : this->infos) {
855 if (ci->id == cid) return ci.get();
856 }
857 return nullptr;
858}
859
860
866{
867 ContentInfo *ci = this->GetContent(cid);
868 if (ci == nullptr || ci->state != ContentInfo::UNSELECTED) return;
869
871 this->CheckDependencyState(*ci);
872}
873
879{
880 ContentInfo *ci = this->GetContent(cid);
881 if (ci == nullptr || !ci->IsSelected()) return;
882
884 this->CheckDependencyState(*ci);
885}
886
889{
890 for (const auto &ci : this->infos) {
891 if (ci->state == ContentInfo::UNSELECTED) {
892 ci->state = ContentInfo::SELECTED;
893 this->CheckDependencyState(*ci);
894 }
895 }
896}
897
900{
901 for (const auto &ci : this->infos) {
902 if (ci->state == ContentInfo::UNSELECTED && ci->upgrade) {
903 ci->state = ContentInfo::SELECTED;
904 this->CheckDependencyState(*ci);
905 }
906 }
907}
908
911{
912 for (const auto &ci : this->infos) {
913 if (ci->IsSelected() && ci->state != ContentInfo::ALREADY_HERE) ci->state = ContentInfo::UNSELECTED;
914 }
915}
916
919{
920 switch (ci.state) {
923 this->Unselect(ci.id);
924 break;
925
927 this->Select(ci.id);
928 break;
929
930 default:
931 break;
932 }
933}
934
941{
942 auto range = this->reverse_dependency_map.equal_range(child.id);
943
944 for (auto iter = range.first; iter != range.second; ++iter) {
945 parents.push_back(GetContent(iter->second));
946 }
947}
948
955{
956 tree.push_back(child);
957
958 /* First find all direct parents. We can't use the "normal" iterator as
959 * we are including stuff into the vector and as such the vector's data
960 * store can be reallocated (and thus move), which means out iterating
961 * pointer gets invalid. So fall back to the indices. */
962 for (const ContentInfo *ci : tree) {
963 ConstContentVector parents;
964 this->ReverseLookupDependency(parents, *ci);
965
966 for (const ContentInfo *ci : parents) {
967 include(tree, ci);
968 }
969 }
970}
971
977{
978 if (ci.IsSelected() || ci.state == ContentInfo::ALREADY_HERE) {
979 /* Selection is easy; just walk all children and set the
980 * autoselected state. That way we can see what we automatically
981 * selected and thus can unselect when a dependency is removed. */
982 for (auto &dependency : ci.dependencies) {
983 ContentInfo *c = this->GetContent(dependency);
984 if (c == nullptr) {
985 this->DownloadContentInfo(dependency);
986 } else if (c->state == ContentInfo::UNSELECTED) {
988 this->CheckDependencyState(*c);
989 }
990 }
991 return;
992 }
993
994 if (ci.state != ContentInfo::UNSELECTED) return;
995
996 /* For unselection we need to find the parents of us. We need to
997 * unselect them. After that we unselect all children that we
998 * depend on and are not used as dependency for us, but only when
999 * we automatically selected them. */
1000 ConstContentVector parents;
1001 this->ReverseLookupDependency(parents, ci);
1002 for (const ContentInfo *c : parents) {
1003 if (!c->IsSelected()) continue;
1004
1005 this->Unselect(c->id);
1006 }
1007
1008 for (auto &dependency : ci.dependencies) {
1009 const ContentInfo *c = this->GetContent(dependency);
1010 if (c == nullptr) {
1011 DownloadContentInfo(dependency);
1012 continue;
1013 }
1014 if (c->state != ContentInfo::AUTOSELECTED) continue;
1015
1016 /* Only unselect when WE are the only parent. */
1017 parents.clear();
1018 this->ReverseLookupDependency(parents, *c);
1019
1020 /* First check whether anything depends on us */
1021 int sel_count = 0;
1022 bool force_selection = false;
1023 for (const ContentInfo *parent_ci : parents) {
1024 if (parent_ci->IsSelected()) sel_count++;
1025 if (parent_ci->state == ContentInfo::SELECTED) force_selection = true;
1026 }
1027 if (sel_count == 0) {
1028 /* Nothing depends on us */
1029 this->Unselect(c->id);
1030 continue;
1031 }
1032 /* Something manually selected depends directly on us */
1033 if (force_selection) continue;
1034
1035 /* "Flood" search to find all items in the dependency graph*/
1036 parents.clear();
1037 this->ReverseLookupTreeDependency(parents, c);
1038
1039 /* Is there anything that is "force" selected?, if so... we're done. */
1040 for (const ContentInfo *parent_ci : parents) {
1041 if (parent_ci->state != ContentInfo::SELECTED) continue;
1042
1043 force_selection = true;
1044 break;
1045 }
1046
1047 /* So something depended directly on us */
1048 if (force_selection) continue;
1049
1050 /* Nothing depends on us, mark the whole graph as unselected.
1051 * After that's done run over them once again to test their children
1052 * to unselect. Don't do it immediately because it'll do exactly what
1053 * we're doing now. */
1054 for (const ContentInfo *parent : parents) {
1055 if (parent->state == ContentInfo::AUTOSELECTED) this->Unselect(parent->id);
1056 }
1057 for (const ContentInfo *parent : parents) {
1058 this->CheckDependencyState(*this->GetContent(parent->id));
1059 }
1060 }
1061}
1062
1065{
1066 this->infos.clear();
1067 this->requested.clear();
1068 this->queued.clear();
1069 this->reverse_dependency_map.clear();
1070}
1071
1072/*** CALLBACK ***/
1073
1075{
1076 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1077 ContentCallback *cb = this->callbacks[i];
1078 /* the callback may remove itself from this->callbacks */
1079 cb->OnConnect(success);
1080 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1081 }
1082}
1083
1085{
1086 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1087 ContentCallback *cb = this->callbacks[i];
1088 cb->OnDisconnect();
1089 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1090 }
1091}
1092
1094{
1095 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1096 ContentCallback *cb = this->callbacks[i];
1097 /* the callback may add items and/or remove itself from this->callbacks */
1098 cb->OnReceiveContentInfo(ci);
1099 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1100 }
1101}
1102
1104{
1105 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1106 ContentCallback *cb = this->callbacks[i];
1107 cb->OnDownloadProgress(ci, bytes);
1108 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1109 }
1110}
1111
1113{
1114 ContentInfo *ci = this->GetContent(cid);
1115 if (ci != nullptr) {
1117 }
1118
1119 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1120 ContentCallback *cb = this->callbacks[i];
1121 /* the callback may remove itself from this->callbacks */
1122 cb->OnDownloadComplete(cid);
1123 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1124 }
1125}
static bool HasAI(const ContentInfo &ci, bool md5sum)
Wrapper function for AIScanner::HasAI.
Definition ai_core.cpp:337
static bool HasSet(const ContentInfo &ci, bool md5sum)
Check whether we have an set with the exact characteristics as ci.
Socket handler for the content server connection.
void SelectUpgrade()
Select everything that's an update for something we've got.
void OnConnect(bool success) override
Callback for when the connection has finished.
void DownloadSelectedContentHTTP(const ContentIDList &content)
Initiate downloading the content over HTTP.
void DownloadSelectedContent(uint &files, uint &bytes, bool fallback=false)
Actually begin downloading the content we selected.
void ToggleSelectedState(const ContentInfo &ci)
Toggle the state of a content info and check its dependencies.
static constexpr std::chrono::seconds IDLE_TIMEOUT
The idle timeout; when to close the connection because it's idle.
void OnReceiveData(std::unique_ptr< char[]> data, size_t length) override
We're receiving data.
void Select(ContentID cid)
Select a specific content id.
void OnReceiveContentInfo(const ContentInfo &ci) override
We received a content info.
int http_response_index
Where we are, in the response, with handling it.
NetworkRecvStatus CloseConnection(bool error=true) override
Disconnect from the content server.
ContentIDList requested
ContentIDs we already requested (so we don't do it again)
void UnselectAll()
Unselect everything that we've not downloaded so far.
void RequestContentList(ContentType type)
Request the content list for the given type.
bool is_connecting
Whether we're connecting.
std::vector< char > http_response
The HTTP response to the requests we've been doing.
void ReverseLookupTreeDependency(ConstContentVector &tree, const ContentInfo *child) const
Reverse lookup the dependencies of all parents over a given child.
ContentIDList queued
ContentID queue to be requested.
void OnDownloadProgress(const ContentInfo &ci, int bytes) override
We have progress in the download of a file.
void DownloadContentInfo(ContentID cid)
Download information of a given Content ID if not already tried.
bool Receive_SERVER_INFO(Packet &p) override
Server sending list of content info: uint8_t type (invalid ID == does not exist) uint32_t id uint32_t...
void SendReceive()
Check whether we received/can send some data from/to the content server and when that's the case hand...
void OnDownloadComplete(ContentID cid) override
We have finished downloading a file.
ContentVector infos
All content info we received.
void OnFailure() override
An error has occurred and the connection has been closed.
std::vector< ContentID > ContentIDList
List of content IDs to (possibly) select.
ContentInfo * GetContent(ContentID cid) const
Get the content info based on a ContentID.
bool Receive_SERVER_CONTENT(Packet &p) override
Server sending list of content info: uint32_t unique id uint32_t file size (0 == does not exist) stri...
void Connect()
Connect with the content server.
std::unordered_multimap< ContentID, ContentID > reverse_dependency_map
Content reverse dependency map.
bool BeforeDownload()
Handle the opening of the file before downloading.
void CheckDependencyState(const ContentInfo &ci)
Check the dependencies (recursively) of this content info.
void Clear()
Clear all downloaded content information.
void RequestQueuedContentInfo()
Send a content request for queued content info download.
std::optional< FileHandle > cur_file
Currently downloaded file.
std::chrono::steady_clock::time_point last_activity
The last time there was network activity.
void AfterDownload()
Handle the closing and extracting of a file after downloading it has been done.
void ReverseLookupDependency(ConstContentVector &parents, const ContentInfo &child) const
Reverse lookup the dependencies of (direct) parents over a given child.
bool IsCancelled() const override
Check if there is a request to cancel the transfer.
std::vector< ContentCallback * > callbacks
Callbacks to notify "the world".
void DownloadSelectedContentFallback(const ContentIDList &content)
Initiate downloading the content over the fallback protocol.
void Unselect(ContentID cid)
Unselect a specific content id.
void Cancel()
Cancel the current download.
void OnDisconnect() override
Callback for when the connection got disconnected.
std::unique_ptr< ContentInfo > cur_info
Information about the currently downloaded file.
bool is_cancelled
Whether the download has been cancelled.
void SelectAll()
Select everything we can select.
static std::optional< FileHandle > Open(const std::string &filename, const std::string &mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1169
static bool HasGame(const ContentInfo &ci, bool md5sum)
Wrapper function for GameScanner::HasGame.
Connect to the content server.
void OnFailure() override
Callback for when the connection attempt failed.
void OnConnect(SOCKET s) override
Callback when the connection succeeded.
NetworkContentConnecter(const std::string &connection_string)
Initiate the connecting.
bool ReceivePackets()
Receive a packet at TCP level.
static void Connect(const std::string &uri, HTTPCallback *callback, const std::string data="")
Connect to the given URI.
Definition http_curl.cpp:93
void Reopen()
Reopen the socket so we can send/receive stuff again.
Definition core.h:79
virtual NetworkRecvStatus CloseConnection(bool error=true)
This will put this socket handler in a close state.
Definition tcp.cpp:41
SOCKET sock
The socket currently connected to.
Definition tcp.h:38
virtual void SendPacket(std::unique_ptr< Packet > &&packet)
This function puts the packet in the send-queue and it is send as soon as possible.
Definition tcp.cpp:58
SendPacketsState SendPackets(bool closing_down=false)
Sends all the buffered packets out for this client.
Definition tcp.cpp:76
void CloseSocket()
Close the actual socket of the connection.
Definition tcp.cpp:29
bool CanSendReceive()
Check whether this socket can send or receive something.
Definition tcp.cpp:194
"Helper" class for creating TCP connections in a non-blocking manner
Definition tcp.h:74
std::string connection_string
Current address we are connecting to (before resolving).
Definition tcp.h:103
Helper for scanning for files with tar as extension.
Definition fileio_func.h:59
bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename={}) override
Add a file with the given filename.
Definition fileio.cpp:437
A timeout timer will fire once after the interval.
Definition timer.h:116
void Reset()
Reset the timer, so it will fire again after the timeout.
Definition timer.h:140
const char * NetworkContentMirrorUriString()
Get the URI string for the content mirror from the environment variable OTTD_CONTENT_MIRROR_URI,...
Definition config.cpp:65
const char * NetworkContentServerConnectionString()
Get the connection string for the content server from the environment variable OTTD_CONTENT_SERVER_CS...
Definition config.cpp:55
static const uint16_t NETWORK_CONTENT_SERVER_PORT
The default port of the content server (TCP)
Definition config.h:24
static const uint NETWORK_CONTENT_URL_LENGTH
The maximum length of a content's url, in bytes including '\0'.
Definition config.h:65
static const uint NETWORK_CONTENT_VERSION_LENGTH
The maximum length of a content's version, in bytes including '\0'.
Definition config.h:64
static const size_t TCP_MTU
Number of bytes we can pack in a single TCP packet.
Definition config.h:45
static const uint NETWORK_CONTENT_FILENAME_LENGTH
The maximum length of a content's filename, in bytes including '\0'.
Definition config.h:62
static const uint NETWORK_CONTENT_DESC_LENGTH
The maximum length of a content's description, in bytes including '\0'.
Definition config.h:66
static const uint NETWORK_CONTENT_TAG_LENGTH
The maximum length of a content's tag, in bytes including '\0'.
Definition config.h:67
static const uint NETWORK_CONTENT_NAME_LENGTH
The maximum length of a content's name, in bytes including '\0'.
Definition config.h:63
bool include(Container &container, typename Container::const_reference &item)
Helper function to append an item to a container if it is not already contained.
NetworkRecvStatus
Status of a network client; reasons why a client has quit.
Definition core.h:23
@ NETWORK_RECV_STATUS_OKAY
Everything is okay.
Definition core.h:24
@ WL_ERROR
Errors (eg. saving/loading failed)
Definition error.h:26
void ShowErrorMessage(EncodedString &&summary_msg, int x, int y, CommandCost &cc)
Display an error message in a window.
bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
Extract the tar with the given filename in the directory where the tar resides.
Definition fileio.cpp:589
bool FioRemove(const std::string &filename)
Remove a file.
Definition fileio.cpp:327
@ SP_AUTODOWNLOAD_DIR
Search within the autodownload directory.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
@ NO_DIRECTORY
A path without any base directory.
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
bool(const ContentInfo &ci, bool md5sum) HasContentProc
Check whether a function piece of content is locally known.
static bool HasGRFConfig(const ContentInfo &ci, bool md5sum)
Wrapper function for the HasProc.
static ssize_t TransferOutFWrite(std::optional< FileHandle > &file, const char *buffer, size_t amount)
Simple wrapper around fwrite to be able to pass it to Packet's TransferOut.
ClientNetworkContentSocketHandler _network_content_client
The client we use to connect to the server.
static HasContentProc * GetHasContentProcforContentType(ContentType type)
Get the has-content check function for the given content type.
bool HasScenario(const ContentInfo &ci, bool md5sum)
Check whether we've got a given scenario based on its unique ID.
Definition fios.cpp:703
static bool GunzipFile(const ContentInfo &ci)
Gunzip a given file and remove the .gz if successful.
static constexpr auto CONTENT_QUEUE_TIMEOUT
Timeout after queueing content for it to try to be requested.
static std::string GetFullFilename(const ContentInfo &ci, bool compressed)
Determine the full filename of a piece of content information.
Part of the network protocol handling content distribution.
std::vector< std::unique_ptr< ContentInfo > > ContentVector
Vector with content info.
std::vector< const ContentInfo * > ConstContentVector
Vector with constant content info.
const GRFConfig * FindGRFConfig(uint32_t grfid, FindGRFConfigMode mode, const MD5Hash *md5sum, uint32_t desired_version)
Find a NewGRF in the scanned list.
@ FGCM_ANY
Use first found.
@ FGCM_EXACT
Only find Grfs matching md5sum.
uint16_t PacketSize
Size of the whole packet.
Definition packet.h:21
@ ReplaceWithQuestionMark
Replace the unknown/bad bits with question marks.
@ AllowNewline
Allow newlines; replaces '\r ' with ' ' during processing.
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:91
Callbacks for notifying others about incoming data.
virtual void OnDownloadProgress(const ContentInfo &ci, int bytes)
We have progress in the download of a file.
virtual void OnDisconnect()
Callback for when the connection got disconnected.
virtual void OnReceiveContentInfo(const ContentInfo &ci)
We received a content info.
virtual void OnDownloadComplete(ContentID cid)
We have finished downloading a file.
virtual void OnConnect(bool success)
Callback for when the connection has finished.
Container for all important information about a piece of content.
uint32_t unique_id
Unique ID; either GRF ID or shortname.
MD5Hash md5sum
The MD5 checksum.
State state
Whether the content info is selected (for download)
ContentID id
Unique (server side) ID for the content.
std::string filename
Filename (for the .tar.gz; only valid on download)
bool IsSelected() const
Is the state either selected or autoselected?
ContentType type
Type of content.
std::vector< ContentID > dependencies
The dependencies (unique server side ids)
@ DOES_NOT_EXIST
The content does not exist in the content system.
@ ALREADY_HERE
The content is already at the client side.
@ AUTOSELECTED
The content has been selected as dependency.
@ UNSELECTED
The content has not been selected.
@ SELECTED
The content has been manually selected.
Internal entity of a packet.
Definition packet.h:43
size_t Recv_bytes(std::span< uint8_t > span)
Extract at most the length of the span bytes from the packet into the span.
Definition packet.cpp:403
uint32_t Recv_uint32()
Read a 32 bits integer from the packet.
Definition packet.cpp:347
std::string Recv_string(size_t length, StringValidationSettings settings=StringValidationSetting::ReplaceWithQuestionMark)
Reads characters (bytes) from the packet until it finds a '\0', or reaches a maximum of length charac...
Definition packet.cpp:425
uint8_t Recv_uint8()
Read a 8 bits integer from the packet.
Definition packet.cpp:318
size_t RemainingBytesToTransfer() const
Get the amount of bytes that are still available for the Transfer functions.
Definition packet.cpp:447
ssize_t TransferOut(F transfer_function, D destination, Args &&... args)
Transfer data from the packet to the given function.
Definition packet.h:141
Subdirectory GetContentInfoSubDir(ContentType type)
Helper to get the subdirectory a ContentInfo is located in.
uint32_t ContentID
Unique identifier for the content.
ContentType
The values in the enum are important; they are used as database 'keys'.
@ CONTENT_TYPE_AI_LIBRARY
The content consists of an AI library.
@ CONTENT_TYPE_BASE_SOUNDS
The content consists of base sounds.
@ CONTENT_TYPE_GAME_LIBRARY
The content consists of a GS library.
@ CONTENT_TYPE_BASE_GRAPHICS
The content consists of base graphics.
@ CONTENT_TYPE_AI
The content consists of an AI.
@ CONTENT_TYPE_SCENARIO
The content consists of a scenario.
@ CONTENT_TYPE_NEWGRF
The content consists of a NewGRF.
@ CONTENT_TYPE_BASE_MUSIC
The content consists of base music.
@ CONTENT_TYPE_GAME
The content consists of a game script.
@ CONTENT_TYPE_END
Helper to mark the end of the types.
@ CONTENT_TYPE_HEIGHTMAP
The content consists of a heightmap.
@ PACKET_CONTENT_CLIENT_CONTENT
Request a content file given an internal ID.
@ PACKET_CONTENT_CLIENT_INFO_LIST
Queries the content server for a list of info of a given content type.
@ PACKET_CONTENT_CLIENT_INFO_EXTID_MD5
Queries the content server for information about a list of external IDs and MD5.
@ PACKET_CONTENT_CLIENT_INFO_ID
Queries the content server for information about a list of internal IDs.
@ PACKET_CONTENT_CLIENT_INFO_EXTID
Queries the content server for information about a list of external IDs.
void CloseWindowById(WindowClass cls, WindowNumber number, bool force, int data)
Close a window by its class and window number (if it is open).
Definition window.cpp:1143
@ WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD
Network content download status.
Definition window_type.h:42
@ WC_NETWORK_STATUS_WINDOW
Network status window; Window numbers: