OpenTTD Source 20250312-master-gcdcc6b491d
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 "../settings_type.h"
19#include "../strings_func.h"
20#include "network_content.h"
21
22#include "table/strings.h"
23
24#if defined(WITH_ZLIB)
25# include <zlib.h>
26# if defined(_WIN32)
27 /* Required for: dup, fileno, close */
28# include <io.h>
29# endif
30#endif
31
32#ifdef __EMSCRIPTEN__
33# include <emscripten.h>
34#endif
35
36#include "../safeguards.h"
37
38extern bool HasScenario(const ContentInfo *ci, bool md5sum);
39
42
44static bool HasGRFConfig(const ContentInfo *ci, bool md5sum)
45{
46 return FindGRFConfig(std::byteswap(ci->unique_id), md5sum ? FGCM_EXACT : FGCM_ANY, md5sum ? &ci->md5sum : nullptr) != nullptr;
47}
48
56typedef bool (*HasProc)(const ContentInfo *ci, bool md5sum);
57
59{
60 ContentInfo *ci = new ContentInfo();
61 ci->type = (ContentType)p.Recv_uint8();
62 ci->id = (ContentID)p.Recv_uint32();
63 ci->filesize = p.Recv_uint32();
64
69
70 ci->unique_id = p.Recv_uint32();
71 p.Recv_bytes(ci->md5sum);
72
73 uint dependency_count = p.Recv_uint8();
74 ci->dependencies.reserve(dependency_count);
75 for (uint i = 0; i < dependency_count; i++) {
76 ContentID dependency_cid = (ContentID)p.Recv_uint32();
77 ci->dependencies.push_back(dependency_cid);
78 this->reverse_dependency_map.emplace(dependency_cid, ci->id);
79 }
80
81 uint tag_count = p.Recv_uint8();
82 ci->tags.reserve(tag_count);
83 for (uint i = 0; i < tag_count; i++) ci->tags.push_back(p.Recv_string(NETWORK_CONTENT_TAG_LENGTH));
84
85 if (!ci->IsValid()) {
86 delete ci;
87 this->CloseConnection();
88 return false;
89 }
90
91 /* Find the appropriate check function */
92 HasProc proc = nullptr;
93 switch (ci->type) {
95 proc = HasGRFConfig;
96 break;
97
100 break;
101
103 proc = BaseMusic::HasSet;
104 break;
105
107 proc = BaseSounds::HasSet;
108 break;
109
110 case CONTENT_TYPE_AI:
111 proc = AI::HasAI; break;
112 break;
113
115 proc = AI::HasAILibrary; break;
116 break;
117
119 proc = Game::HasGame; break;
120 break;
121
123 proc = Game::HasGameLibrary; break;
124 break;
125
128 proc = HasScenario;
129 break;
130
131 default:
132 break;
133 }
134
135 if (proc != nullptr) {
136 if (proc(ci, true)) {
138 } else {
140 if (proc(ci, false)) ci->upgrade = true;
141 }
142 } else {
144 }
145
146 /* Something we don't have and has filesize 0 does not exist in the system */
148
149 /* Do we already have a stub for this? */
150 for (ContentInfo *ici : this->infos) {
151 if (ici->type == ci->type && ici->unique_id == ci->unique_id && ci->md5sum == ici->md5sum) {
152 /* Preserve the name if possible */
153 if (ci->name.empty()) ci->name = ici->name;
154 if (ici->IsSelected()) ci->state = ici->state;
155
156 /*
157 * As ici might be selected by the content window we cannot delete that.
158 * However, we want to keep most of the values of ci, except the values
159 * we (just) already preserved.
160 */
161 *ici = *ci;
162 delete ci;
163
164 this->OnReceiveContentInfo(ici);
165 return true;
166 }
167 }
168
169 /* Missing content info? Don't list it */
170 if (ci->filesize == 0) {
171 delete ci;
172 return true;
173 }
174
175 this->infos.push_back(ci);
176
177 /* Incoming data means that we might need to reconsider dependencies */
178 ConstContentVector parents;
179 this->ReverseLookupTreeDependency(parents, ci);
180 for (const ContentInfo *ici : parents) {
181 this->CheckDependencyState(ici);
182 }
183
184 this->OnReceiveContentInfo(ci);
185
186 return true;
187}
188
194{
195 if (type == CONTENT_TYPE_END) {
206 return;
207 }
208
209 this->Connect();
210
211 auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_INFO_LIST);
212 p->Send_uint8 ((uint8_t)type);
213 p->Send_uint32(0xffffffff);
214 p->Send_uint8 (1);
215 p->Send_string("vanilla");
216 p->Send_string(_openttd_content_version);
217
218 /* Patchpacks can extend the list with one. In BaNaNaS metadata you can
219 * add a branch in the 'compatibility' list, to filter on this. If you want
220 * your patchpack to be mentioned in the BaNaNaS web-interface, create an
221 * issue on https://github.com/OpenTTD/bananas-api asking for this.
222
223 p->Send_string("patchpack"); // Or what-ever the name of your patchpack is.
224 p->Send_string(_openttd_content_version_patchpack);
225
226 */
227
228 this->SendPacket(std::move(p));
229}
230
237{
238 this->Connect();
239
240 while (count > 0) {
241 /* We can "only" send a limited number of IDs in a single packet.
242 * A packet begins with the packet size and a byte for the type.
243 * Then this packet adds a uint16_t for the count in this packet.
244 * The rest of the packet can be used for the IDs. */
245 uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint16_t)) / sizeof(uint32_t));
246
247 auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_INFO_ID, TCP_MTU);
248 p->Send_uint16(p_count);
249
250 for (uint i = 0; i < p_count; i++) {
251 p->Send_uint32(content_ids[i]);
252 }
253
254 this->SendPacket(std::move(p));
255 count -= p_count;
256 content_ids += p_count;
257 }
258}
259
266{
267 if (cv == nullptr) return;
268
269 this->Connect();
270
271 assert(cv->size() < 255);
272 assert(cv->size() < (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint8_t)) /
273 (sizeof(uint8_t) + sizeof(uint32_t) + (send_md5sum ? MD5_HASH_BYTES : 0)));
274
275 auto p = std::make_unique<Packet>(this, send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID, TCP_MTU);
276 p->Send_uint8((uint8_t)cv->size());
277
278 for (const ContentInfo *ci : *cv) {
279 p->Send_uint8((uint8_t)ci->type);
280 p->Send_uint32(ci->unique_id);
281 if (!send_md5sum) continue;
282 p->Send_bytes(ci->md5sum);
283 }
284
285 this->SendPacket(std::move(p));
286
287 for (ContentInfo *ci : *cv) {
288 bool found = false;
289 for (ContentInfo *ci2 : this->infos) {
290 if (ci->type == ci2->type && ci->unique_id == ci2->unique_id &&
291 (!send_md5sum || ci->md5sum == ci2->md5sum)) {
292 found = true;
293 break;
294 }
295 }
296 if (!found) {
297 this->infos.push_back(ci);
298 } else {
299 delete ci;
300 }
301 }
302}
303
310void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes, bool fallback)
311{
312 bytes = 0;
313
314 ContentIDList content;
315 for (const ContentInfo *ci : this->infos) {
316 if (!ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) continue;
317
318 content.push_back(ci->id);
319 bytes += ci->filesize;
320 }
321
322 files = (uint)content.size();
323
324 /* If there's nothing to download, do nothing. */
325 if (files == 0) return;
326
327 this->isCancelled = false;
328
329 if (fallback) {
330 this->DownloadSelectedContentFallback(content);
331 } else {
332 this->DownloadSelectedContentHTTP(content);
333 }
334}
335
341{
342 std::string content_request;
343 for (const ContentID &id : content) {
344 content_request += std::to_string(id) + "\n";
345 }
346
347 this->http_response_index = -1;
348
350}
351
357{
358 uint count = (uint)content.size();
359 const ContentID *content_ids = content.data();
360 this->Connect();
361
362 while (count > 0) {
363 /* We can "only" send a limited number of IDs in a single packet.
364 * A packet begins with the packet size and a byte for the type.
365 * Then this packet adds a uint16_t for the count in this packet.
366 * The rest of the packet can be used for the IDs. */
367 uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint16_t)) / sizeof(uint32_t));
368
369 auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_CONTENT, TCP_MTU);
370 p->Send_uint16(p_count);
371
372 for (uint i = 0; i < p_count; i++) {
373 p->Send_uint32(content_ids[i]);
374 }
375
376 this->SendPacket(std::move(p));
377 count -= p_count;
378 content_ids += p_count;
379 }
380}
381
389static std::string GetFullFilename(const ContentInfo *ci, bool compressed)
390{
392 if (dir == NO_DIRECTORY) return {};
393
394 std::string buf = FioGetDirectory(SP_AUTODOWNLOAD_DIR, dir);
395 buf += ci->filename;
396 buf += compressed ? ".tar.gz" : ".tar";
397
398 return buf;
399}
400
406static bool GunzipFile(const ContentInfo *ci)
407{
408#if defined(WITH_ZLIB)
409 bool ret = true;
410
411 /* Need to open the file with fopen() to support non-ASCII on Windows. */
412 auto ftmp = FileHandle::Open(GetFullFilename(ci, true), "rb");
413 if (!ftmp.has_value()) return false;
414 /* Duplicate the handle, and close the FILE*, to avoid double-closing the handle later. */
415 int fdup = dup(fileno(*ftmp));
416 gzFile fin = gzdopen(fdup, "rb");
417 ftmp.reset();
418
419 auto fout = FileHandle::Open(GetFullFilename(ci, false), "wb");
420
421 if (fin == nullptr || !fout.has_value()) {
422 ret = false;
423 } else {
424 uint8_t buff[8192];
425 for (;;) {
426 int read = gzread(fin, buff, sizeof(buff));
427 if (read == 0) {
428 /* If gzread() returns 0, either the end-of-file has been
429 * reached or an underlying read error has occurred.
430 *
431 * gzeof() can't be used, because:
432 * 1.2.5 - it is safe, 1 means 'everything was OK'
433 * 1.2.3.5, 1.2.4 - 0 or 1 is returned 'randomly'
434 * 1.2.3.3 - 1 is returned for truncated archive
435 *
436 * So we use gzerror(). When proper end of archive
437 * has been reached, then:
438 * errnum == Z_STREAM_END in 1.2.3.3,
439 * errnum == 0 in 1.2.4 and 1.2.5 */
440 int errnum;
441 gzerror(fin, &errnum);
442 if (errnum != 0 && errnum != Z_STREAM_END) ret = false;
443 break;
444 }
445 if (read < 0 || static_cast<size_t>(read) != fwrite(buff, 1, read, *fout)) {
446 /* If gzread() returns -1, there was an error in archive */
447 ret = false;
448 break;
449 }
450 /* DO NOT DO THIS! It will fail to detect broken archive with 1.2.3.3!
451 * if (read < sizeof(buff)) break; */
452 }
453 }
454
455 if (fin != nullptr) {
456 gzclose(fin);
457 } else if (fdup != -1) {
458 /* Failing gzdopen does not close the passed file descriptor. */
459 close(fdup);
460 }
461
462 return ret;
463#else
464 NOT_REACHED();
465#endif /* defined(WITH_ZLIB) */
466}
467
475static inline ssize_t TransferOutFWrite(std::optional<FileHandle> &file, const char *buffer, size_t amount)
476{
477 return fwrite(buffer, 1, amount, *file);
478}
479
481{
482 if (!this->curFile.has_value()) {
483 delete this->curInfo;
484 /* When we haven't opened a file this must be our first packet with metadata. */
485 this->curInfo = new ContentInfo;
486 this->curInfo->type = (ContentType)p.Recv_uint8();
487 this->curInfo->id = (ContentID)p.Recv_uint32();
488 this->curInfo->filesize = p.Recv_uint32();
490
491 if (!this->BeforeDownload()) {
492 this->CloseConnection();
493 return false;
494 }
495 } else {
496 /* We have a file opened, thus are downloading internal content */
497 size_t toRead = p.RemainingBytesToTransfer();
498 if (toRead != 0 && static_cast<size_t>(p.TransferOut(TransferOutFWrite, std::ref(this->curFile))) != toRead) {
501 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD),
502 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE),
503 WL_ERROR);
504 this->CloseConnection();
505 this->curFile.reset();
506
507 return false;
508 }
509
510 this->OnDownloadProgress(this->curInfo, (int)toRead);
511
512 if (toRead == 0) this->AfterDownload();
513 }
514
515 return true;
516}
517
523{
524 if (!this->curInfo->IsValid()) {
525 delete this->curInfo;
526 this->curInfo = nullptr;
527 return false;
528 }
529
530 if (this->curInfo->filesize != 0) {
531 /* The filesize is > 0, so we are going to download it */
532 std::string filename = GetFullFilename(this->curInfo, true);
533 if (filename.empty() || !(this->curFile = FileHandle::Open(filename, "wb")).has_value()) {
534 /* Unless that fails of course... */
537 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD),
538 GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE),
539 WL_ERROR);
540 return false;
541 }
542 }
543 return true;
544}
545
551{
552 /* We read nothing; that's our marker for end-of-stream.
553 * Now gunzip the tar and make it known. */
554 this->curFile.reset();
555
556 if (GunzipFile(this->curInfo)) {
557 FioRemove(GetFullFilename(this->curInfo, true));
558
560 if (sd == NO_DIRECTORY) NOT_REACHED();
561
562 TarScanner ts;
563 std::string fname = GetFullFilename(this->curInfo, false);
564 ts.AddFile(sd, fname);
565
566 if (this->curInfo->type == CONTENT_TYPE_BASE_MUSIC) {
567 /* Music can't be in a tar. So extract the tar! */
568 ExtractTar(fname, BASESET_DIR);
569 FioRemove(fname);
570 }
571
572#ifdef __EMSCRIPTEN__
573 EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
574#endif
575
576 this->OnDownloadComplete(this->curInfo->id);
577 } else {
578 ShowErrorMessage(GetEncodedString(STR_CONTENT_ERROR_COULD_NOT_EXTRACT), {}, WL_ERROR);
579 }
580}
581
583{
584 return this->isCancelled;
585}
586
587/* Also called to just clean up the mess. */
589{
590 this->http_response.clear();
591 this->http_response.shrink_to_fit();
592 this->http_response_index = -2;
593
594 if (this->curFile.has_value()) {
595 this->OnDownloadProgress(this->curInfo, -1);
596
597 this->curFile.reset();
598 }
599
600 /* If we fail, download the rest via the 'old' system. */
601 if (!this->isCancelled) {
602 uint files, bytes;
603
604 this->DownloadSelectedContent(files, bytes, true);
605 }
606}
607
608void ClientNetworkContentSocketHandler::OnReceiveData(std::unique_ptr<char[]> data, size_t length)
609{
610 assert(data.get() == nullptr || length != 0);
611
612 /* Ignore any latent data coming from a connection we closed. */
613 if (this->http_response_index == -2) {
614 return;
615 }
616
617 if (this->http_response_index == -1) {
618 if (data != nullptr) {
619 /* Append the rest of the response. */
620 this->http_response.insert(this->http_response.end(), data.get(), data.get() + length);
621 return;
622 } else {
623 /* Make sure the response is properly terminated. */
624 this->http_response.push_back('\0');
625
626 /* And prepare for receiving the rest of the data. */
627 this->http_response_index = 0;
628 }
629 }
630
631 if (data != nullptr) {
632 /* We have data, so write it to the file. */
633 if (fwrite(data.get(), 1, length, *this->curFile) != length) {
634 /* Writing failed somehow, let try via the old method. */
635 this->OnFailure();
636 } else {
637 /* Just received the data. */
638 this->OnDownloadProgress(this->curInfo, (int)length);
639 }
640
641 /* Nothing more to do now. */
642 return;
643 }
644
645 if (this->curFile.has_value()) {
646 /* We've finished downloading a file. */
647 this->AfterDownload();
648 }
649
650 if ((uint)this->http_response_index >= this->http_response.size()) {
651 /* It's not a real failure, but if there's
652 * nothing more to download it helps with
653 * cleaning up the stuff we allocated. */
654 this->OnFailure();
655 return;
656 }
657
658 delete this->curInfo;
659 /* When we haven't opened a file this must be our first packet with metadata. */
660 this->curInfo = new ContentInfo;
661
663#define check_not_null(p) { if ((p) == nullptr) { this->OnFailure(); return; } }
665#define check_and_terminate(p) { check_not_null(p); *(p) = '\0'; }
666
667 for (;;) {
668 char *str = this->http_response.data() + this->http_response_index;
669 char *p = strchr(str, '\n');
670 check_and_terminate(p);
671
672 /* Update the index for the next one */
673 this->http_response_index += (int)strlen(str) + 1;
674
675 /* Read the ID */
676 p = strchr(str, ',');
677 check_and_terminate(p);
678 this->curInfo->id = (ContentID)atoi(str);
679
680 /* Read the type */
681 str = p + 1;
682 p = strchr(str, ',');
683 check_and_terminate(p);
684 this->curInfo->type = (ContentType)atoi(str);
685
686 /* Read the file size */
687 str = p + 1;
688 p = strchr(str, ',');
689 check_and_terminate(p);
690 this->curInfo->filesize = atoi(str);
691
692 /* Read the URL */
693 str = p + 1;
694 /* Is it a fallback URL? If so, just continue with the next one. */
695 if (strncmp(str, "ottd", 4) == 0) {
696 if ((uint)this->http_response_index >= this->http_response.size()) {
697 /* Have we gone through all lines? */
698 this->OnFailure();
699 return;
700 }
701 continue;
702 }
703
704 p = strrchr(str, '/');
705 check_not_null(p);
706 p++; // Start after the '/'
707
708 std::string filename = p;
709 /* Remove the extension from the string. */
710 for (uint i = 0; i < 2; i++) {
711 auto pos = filename.find_last_of('.');
712 if (pos == std::string::npos) {
713 this->OnFailure();
714 return;
715 }
716 filename.erase(pos);
717 }
718
719 /* Copy the string, without extension, to the filename. */
720 this->curInfo->filename = std::move(filename);
721
722 /* Request the next file. */
723 if (!this->BeforeDownload()) {
724 this->OnFailure();
725 return;
726 }
727
729 return;
730 }
731
732#undef check
733#undef check_and_terminate
734}
735
741 http_response_index(-2),
742 curFile(std::nullopt),
743 curInfo(nullptr),
744 isConnecting(false),
745 isCancelled(false)
746{
747 this->lastActivity = std::chrono::steady_clock::now();
748}
749
752{
753 delete this->curInfo;
754
755 for (ContentInfo *ci : this->infos) delete ci;
756}
757
760public:
766
767 void OnFailure() override
768 {
771 }
772
773 void OnConnect(SOCKET s) override
774 {
775 assert(_network_content_client.sock == INVALID_SOCKET);
776 _network_content_client.lastActivity = std::chrono::steady_clock::now();
781 }
782};
783
788{
789 if (this->sock != INVALID_SOCKET || this->isConnecting) return;
790
791 this->isCancelled = false;
792 this->isConnecting = true;
793
794 TCPConnecter::Create<NetworkContentConnecter>(NetworkContentServerConnectionString());
795}
796
811
816{
817 this->isCancelled = true;
818 this->CloseConnection();
819}
820
826{
827 if (this->sock == INVALID_SOCKET || this->isConnecting) return;
828
829 /* Close the connection to the content server after inactivity; there can still be downloads pending via HTTP. */
830 if (std::chrono::steady_clock::now() > this->lastActivity + IDLE_TIMEOUT) {
831 this->CloseConnection();
832 return;
833 }
834
835 if (this->CanSendReceive()) {
836 if (this->ReceivePackets()) {
837 /* Only update activity once a packet is received, instead of every time we try it. */
838 this->lastActivity = std::chrono::steady_clock::now();
839 }
840 }
841
842 this->SendPackets();
843}
844
850{
851 /* When we tried to download it already, don't try again */
852 if (std::ranges::find(this->requested, cid) != this->requested.end()) return;
853
854 this->requested.push_back(cid);
855 this->RequestContentList(1, &cid);
856}
857
864{
865 for (ContentInfo *ci : this->infos) {
866 if (ci->id == cid) return ci;
867 }
868 return nullptr;
869}
870
871
877{
878 ContentInfo *ci = this->GetContent(cid);
879 if (ci == nullptr || ci->state != ContentInfo::UNSELECTED) return;
880
882 this->CheckDependencyState(ci);
883}
884
890{
891 ContentInfo *ci = this->GetContent(cid);
892 if (ci == nullptr || !ci->IsSelected()) return;
893
895 this->CheckDependencyState(ci);
896}
897
900{
901 for (ContentInfo *ci : this->infos) {
902 if (ci->state == ContentInfo::UNSELECTED) {
903 ci->state = ContentInfo::SELECTED;
904 this->CheckDependencyState(ci);
905 }
906 }
907}
908
911{
912 for (ContentInfo *ci : this->infos) {
913 if (ci->state == ContentInfo::UNSELECTED && ci->upgrade) {
914 ci->state = ContentInfo::SELECTED;
915 this->CheckDependencyState(ci);
916 }
917 }
918}
919
922{
923 for (ContentInfo *ci : this->infos) {
924 if (ci->IsSelected() && ci->state != ContentInfo::ALREADY_HERE) ci->state = ContentInfo::UNSELECTED;
925 }
926}
927
930{
931 switch (ci->state) {
934 this->Unselect(ci->id);
935 break;
936
938 this->Select(ci->id);
939 break;
940
941 default:
942 break;
943 }
944}
945
952{
953 auto range = this->reverse_dependency_map.equal_range(child->id);
954
955 for (auto iter = range.first; iter != range.second; ++iter) {
956 parents.push_back(GetContent(iter->second));
957 }
958}
959
966{
967 tree.push_back(child);
968
969 /* First find all direct parents. We can't use the "normal" iterator as
970 * we are including stuff into the vector and as such the vector's data
971 * store can be reallocated (and thus move), which means out iterating
972 * pointer gets invalid. So fall back to the indices. */
973 for (uint i = 0; i < tree.size(); i++) {
974 ConstContentVector parents;
975 this->ReverseLookupDependency(parents, tree[i]);
976
977 for (const ContentInfo *ci : parents) {
978 include(tree, ci);
979 }
980 }
981}
982
988{
989 if (ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) {
990 /* Selection is easy; just walk all children and set the
991 * autoselected state. That way we can see what we automatically
992 * selected and thus can unselect when a dependency is removed. */
993 for (auto &dependency : ci->dependencies) {
994 ContentInfo *c = this->GetContent(dependency);
995 if (c == nullptr) {
996 this->DownloadContentInfo(dependency);
997 } else if (c->state == ContentInfo::UNSELECTED) {
999 this->CheckDependencyState(c);
1000 }
1001 }
1002 return;
1003 }
1004
1005 if (ci->state != ContentInfo::UNSELECTED) return;
1006
1007 /* For unselection we need to find the parents of us. We need to
1008 * unselect them. After that we unselect all children that we
1009 * depend on and are not used as dependency for us, but only when
1010 * we automatically selected them. */
1011 ConstContentVector parents;
1012 this->ReverseLookupDependency(parents, ci);
1013 for (const ContentInfo *c : parents) {
1014 if (!c->IsSelected()) continue;
1015
1016 this->Unselect(c->id);
1017 }
1018
1019 for (auto &dependency : ci->dependencies) {
1020 const ContentInfo *c = this->GetContent(dependency);
1021 if (c == nullptr) {
1022 DownloadContentInfo(dependency);
1023 continue;
1024 }
1025 if (c->state != ContentInfo::AUTOSELECTED) continue;
1026
1027 /* Only unselect when WE are the only parent. */
1028 parents.clear();
1029 this->ReverseLookupDependency(parents, c);
1030
1031 /* First check whether anything depends on us */
1032 int sel_count = 0;
1033 bool force_selection = false;
1034 for (const ContentInfo *parent_ci : parents) {
1035 if (parent_ci->IsSelected()) sel_count++;
1036 if (parent_ci->state == ContentInfo::SELECTED) force_selection = true;
1037 }
1038 if (sel_count == 0) {
1039 /* Nothing depends on us */
1040 this->Unselect(c->id);
1041 continue;
1042 }
1043 /* Something manually selected depends directly on us */
1044 if (force_selection) continue;
1045
1046 /* "Flood" search to find all items in the dependency graph*/
1047 parents.clear();
1048 this->ReverseLookupTreeDependency(parents, c);
1049
1050 /* Is there anything that is "force" selected?, if so... we're done. */
1051 for (const ContentInfo *parent_ci : parents) {
1052 if (parent_ci->state != ContentInfo::SELECTED) continue;
1053
1054 force_selection = true;
1055 break;
1056 }
1057
1058 /* So something depended directly on us */
1059 if (force_selection) continue;
1060
1061 /* Nothing depends on us, mark the whole graph as unselected.
1062 * After that's done run over them once again to test their children
1063 * to unselect. Don't do it immediately because it'll do exactly what
1064 * we're doing now. */
1065 for (const ContentInfo *parent : parents) {
1066 if (parent->state == ContentInfo::AUTOSELECTED) this->Unselect(parent->id);
1067 }
1068 for (const ContentInfo *parent : parents) {
1069 this->CheckDependencyState(this->GetContent(parent->id));
1070 }
1071 }
1072}
1073
1076{
1077 for (ContentInfo *c : this->infos) delete c;
1078
1079 this->infos.clear();
1080 this->requested.clear();
1081 this->reverse_dependency_map.clear();
1082}
1083
1084/*** CALLBACK ***/
1085
1087{
1088 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1089 ContentCallback *cb = this->callbacks[i];
1090 /* the callback may remove itself from this->callbacks */
1091 cb->OnConnect(success);
1092 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1093 }
1094}
1095
1097{
1098 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1099 ContentCallback *cb = this->callbacks[i];
1100 cb->OnDisconnect();
1101 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1102 }
1103}
1104
1106{
1107 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1108 ContentCallback *cb = this->callbacks[i];
1109 /* the callback may add items and/or remove itself from this->callbacks */
1110 cb->OnReceiveContentInfo(ci);
1111 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1112 }
1113}
1114
1116{
1117 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1118 ContentCallback *cb = this->callbacks[i];
1119 cb->OnDownloadProgress(ci, bytes);
1120 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1121 }
1122}
1123
1125{
1126 ContentInfo *ci = this->GetContent(cid);
1127 if (ci != nullptr) {
1129 }
1130
1131 for (size_t i = 0; i < this->callbacks.size(); /* nothing */) {
1132 ContentCallback *cb = this->callbacks[i];
1133 /* the callback may remove itself from this->callbacks */
1134 cb->OnDownloadComplete(cid);
1135 if (i != this->callbacks.size() && this->callbacks[i] == cb) i++;
1136 }
1137}
static bool HasAI(const struct ContentInfo *ci, bool md5sum)
Wrapper function for AIScanner::HasAI.
Definition ai_core.cpp:345
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.
std::chrono::steady_clock::time_point lastActivity
The last time there was network activity.
void DownloadSelectedContent(uint &files, uint &bytes, bool fallback=false)
Actually begin downloading the content we selected.
bool isCancelled
Whether the download has been cancelled.
void ToggleSelectedState(const ContentInfo *ci)
Toggle the state of a content info and check its dependencies.
ClientNetworkContentSocketHandler()
Create a socket handler to handle the connection.
bool isConnecting
Whether we're connecting.
void OnDownloadProgress(const ContentInfo *ci, int bytes) override
We have progress in the download of a file.
void CheckDependencyState(const ContentInfo *ci)
Check the dependencies (recursively) of this content info.
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.
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.
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.
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.
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 Clear()
Clear all downloaded content information.
ContentInfo * curInfo
Information about the currently downloaded file.
std::optional< FileHandle > curFile
Currently downloaded file.
std::vector< ContentID > ContentIDList
List of content IDs to (possibly) select.
void AfterDownload()
Handle the closing and extracting of a file after downloading it has been done.
bool IsCancelled() const override
Check if there is a request to cancel the transfer.
std::vector< ContentCallback * > callbacks
Callbacks to notify "the world".
~ClientNetworkContentSocketHandler()
Clear up the mess ;)
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.
void ReverseLookupDependency(ConstContentVector &parents, const ContentInfo *child) const
Reverse lookup the dependencies of (direct) parents over a given child.
void SelectAll()
Select everything we can select.
void OnReceiveContentInfo(const ContentInfo *ci) override
We received a content info.
static std::optional< FileHandle > Open(const std::string &filename, const std::string &mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1170
static bool HasGame(const struct 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.
Base socket handler for all Content TCP sockets.
Definition tcp_content.h:22
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:438
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, const 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:590
bool FioRemove(const std::string &filename)
Remove a file.
Definition fileio.cpp:328
@ 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 HasScenario(const ContentInfo *ci, bool md5sum)
Check whether we've got a given scenario based on its unique ID.
Definition fios.cpp:703
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 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 bool GunzipFile(const ContentInfo *ci)
Gunzip a given file and remove the .gz if successful.
bool(* HasProc)(const ContentInfo *ci, bool md5sum)
Check whether a function piece of content is locally known.
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< const ContentInfo * > ConstContentVector
Vector with constant content info.
std::vector< ContentInfo * > ContentVector
Vector with 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
@ SVS_ALLOW_NEWLINE
Allow newlines; replaces '\r ' with ' ' during processing.
Definition string_type.h:47
@ SVS_REPLACE_WITH_QUESTION_MARK
Replace the unknown/bad bits with question marks.
Definition string_type.h:46
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:90
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 OnDownloadComplete(ContentID cid)
We have finished downloading a file.
virtual void OnConnect(bool success)
Callback for when the connection has finished.
virtual void OnReceiveContentInfo(const ContentInfo *ci)
We received a content info.
Container for all important information about a piece of content.
uint32_t unique_id
Unique ID; either GRF ID or shortname.
bool IsValid() const
Is the information from this content info valid?
uint32_t filesize
Size of the file.
MD5Hash md5sum
The MD5 checksum.
std::string url
URL related to the content.
State state
Whether the content info is selected (for download)
std::string name
Name of the content.
std::string description
Description of the content.
std::string version
Version of the content.
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.
StringList tags
Tags associated with the content.
bool upgrade
This item is an upgrade.
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
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
std::string Recv_string(size_t length, StringValidationSettings settings=SVS_REPLACE_WITH_QUESTION_MARK)
Reads characters (bytes) from the packet until it finds a '\0', or reaches a maximum of length charac...
Definition packet.cpp:425
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: