21 #include "C4Version.h"
37 std::string
getSafeStringValue(
const TiXmlElement *xml,
const char *childName, std::string fallback =
"",
bool isAttribute =
false)
39 if (xml ==
nullptr)
return fallback;
42 const char * value = xml->Attribute(childName);
47 const TiXmlElement *child = xml->FirstChildElement(childName);
48 if (child ==
nullptr)
return fallback;
49 const char *nodeText = child->GetText();
50 if (nodeText ==
nullptr)
return fallback;
51 const std::string value(nodeText);
52 if (!value.empty())
return value;
80 const bool descriptionNeedsCut =
description.size() > 150;
81 if (descriptionNeedsCut)
84 std::string::size_type characterCount{ 0 };
85 for (
const char* c =
description.data(); *c !=
'\0';)
87 const uint8_t val = *c;
98 const bool shouldCutHere = characterCount >= 150;
111 const TiXmlElement* dependencies_node = xml->FirstChildElement(
"dependencies");
112 if (dependencies_node !=
nullptr)
113 for (
const TiXmlElement *node = dependencies_node->FirstChildElement(
"item"); node !=
nullptr; node = node->NextSiblingElement(
"item"))
115 const char *depID = node->GetText();
116 if (depID !=
nullptr)
120 const TiXmlElement* tags_node = xml->FirstChildElement(
"tags");
121 if (tags_node !=
nullptr)
122 for (
const TiXmlElement *node = tags_node->FirstChildElement(
"item"); node !=
nullptr; node = node->NextSiblingElement(
"item"))
124 const std::string tag = node->GetText();
129 const TiXmlElement* files_node = xml->FirstChildElement(
"files");
130 if (files_node !=
nullptr)
131 for (
const TiXmlElement *filenode = files_node->FirstChildElement(
"item"); filenode !=
nullptr; filenode = filenode->NextSiblingElement(
"item"))
134 const TiXmlHandle nodeHandle(
const_cast<TiXmlNode*
> (
static_cast<const TiXmlNode*
> (filenode)));
142 if (handle.empty() || name.empty() || lengthString.empty())
continue;
147 length = std::stoi(lengthString);
154 files.emplace_back(
FileInfo{ handle, length, name, hashSHA1 });
167 : pModsDlg(pModsDlg), pList(pForListBox), iInfoIconCount(0)
172 rctIconLarge.
Set(0, 0, iHeight, iHeight);
173 int32_t iSmallIcon = iHeight * 2 / 3; rctIconSmall.
Set((iHeight - iSmallIcon)/2, (iHeight - iSmallIcon)/2, iSmallIcon, iSmallIcon);
183 int32_t iThisWdt = rcIconRect.
Wdt;
184 rcIconRect.
x = iThisWdt - iIconSize * (iInfoIconCount + 1);
185 rcIconRect.
Wdt = rcIconRect.
Hgt = iIconSize;
189 rcIconRect.
x -= rcIconRect.
Wdt;
192 rcLabelBounds.
x = iHeight+3;
193 rcLabelBounds.
Hgt = iLineHgt;
197 for (
int c = 0; c < 2; ++c)
199 const int alignment = alignments[c];
200 rcLabelBounds.
y = 1 + i*(iLineHgt + 2);
201 rcLabelBounds.
Wdt = iThisWdt - rcLabelBounds.
x - 1;
204 rcLabelBounds.
Wdt -= rcIconRect.
x;
206 if (alignment ==
ALeft) pInfoLbl[i] = pLbl;
207 else if (alignment ==
ARight) pInfoLabelsRight[i] = pLbl;
225 modXMLData = std::make_unique<ModXMLData>(xml, source);
226 if (modXMLData->id.empty()) modXMLData->id = fallbackID;
227 if (modXMLData->title.empty()) modXMLData->title = fallbackName;
232 sInfoText[0].
Format(
LoadResStr(
"IDS_MODS_TITLE"), fallbackName.c_str(),
"???");
239 std::string::size_type dateEnd;
240 if (!updated.empty() && (dateEnd = updated.find(
'T')) != std::string::npos)
242 updated = updated.substr(0, dateEnd);
249 const std::string title = modXMLData->title.empty() ?
"???" : modXMLData->title;
250 sInfoText[0].
Format(
LoadResStr(
"IDS_MODS_TITLE"), title.c_str(), author.c_str());
252 bool hasScenario =
false;
253 bool hasObjectPackage =
false;
254 static std::string openclonkVersionStringTag;
255 if (openclonkVersionStringTag.empty())
256 openclonkVersionStringTag = C4StartupModsDlg::GetOpenClonkVersionStringTag();
258 std::ostringstream otherTagsStream;
259 bool isFirstOtherTag =
true;
261 for (
auto &tag : modXMLData->tags)
263 if ((tag ==
".scenario") && !hasScenario)
268 else if (tag ==
".objects")
271 hasObjectPackage =
true;
273 else if (tag ==
"multiplayer")
275 else if (tag == openclonkVersionStringTag)
277 else if (!tag.empty() && tag.front() !=
'.')
279 otherTagsStream << (isFirstOtherTag ?
"" :
", ") << tag;
280 isFirstOtherTag =
false;
284 const std::string &otherTags = otherTagsStream.str();
285 if (!otherTags.empty())
291 if (!modXMLData->metadataMissing && !modXMLData->longDescription.empty())
296 else if (hasObjectPackage)
325 sInfoText[1].
Format(
LoadResStr(
"IDS_MODS_SEARCH_NEXTPAGE_DESC"), page, totalPages, totalResults);
333 sInfoText[1].
Copy(message.c_str());
341 ParentClass::DrawElement(cgo);
349 sInfoText[i].
Clear();
350 sInfoTextRight[i].
Clear();
359 void C4StartupModsListEntry::UpdateEntrySize()
364 for (
int c = 0; c < 2; ++c)
367 C4GUI::Label **labelList = (c == 0) ? pInfoLbl : pInfoLabelsRight;
368 for (
int i = 0; i < iLblCnt; ++i)
385 if (isInfoEntry)
return;
387 isInstalled = modInfo !=
nullptr;
389 std::string fullDescription =
"";
390 if (!modXMLData->metadataMissing)
391 fullDescription = modXMLData->description;
393 fullDescription =
LoadResStr(
"IDS_MODS_METADATA_MISSING");
395 if (modInfo !=
nullptr)
399 fullDescription = std::string(
"<c 559955>") +
LoadResStr(
"IDS_MODS_INSTALLED") +
".</c> " + fullDescription;
406 sInfoText[1].
Format(
"%s", fullDescription.c_str());
410 void C4StartupModsListEntry::UpdateText()
412 bool fRestackElements=
false;
423 for (
int c = 0; c < 2; ++c)
425 C4GUI::Label **infoLabels = (c == 0) ? pInfoLbl : pInfoLabelsRight;
426 StdStrBuf *infoTexts = (c == 0) ? sInfoText : sInfoTextRight;
431 if (!i) iAvailableWdt -= sx;
433 pUseFont->
BreakMessage(infoTexts[i].getData(), iAvailableWdt, &BrokenText,
true);
443 fRestackElements =
true;
450 if (fRestackElements) UpdateEntrySize();
454 bool fChange = fToValue !=
fVisible;
456 if(fChange) UpdateEntrySize();
459 void C4StartupModsListEntry::AddStatusIcon(
C4GUI::Icons eIcon,
const char *szToolTip,
bool insertLeft)
464 auto insertPosition = iInfoIconCount;
469 for (
auto i = iInfoIconCount - 1; i >= 0; --i)
471 pInfoIcons[i + 1]->
SetFacet(pInfoIcons[i]->GetFacet());
477 pInfoIcons[insertPosition]->
SetIcon(eIcon);
478 pInfoIcons[insertPosition]->
SetToolTip(szToolTip);
484 assert(!discoveryFinished);
485 discoveryFinishedEvent.
Reset();
489 discoveryFinished =
true;
490 discoveryFinishedEvent.
Set();
497 void C4StartupModsLocalModDiscovery::ExecuteDiscovery()
503 const std::string filename(*iter);
509 if (lastSeparaterPosition == std::string::npos)
continue;
510 const std::string leaf = filename.substr(lastSeparaterPosition + 1);
512 const size_t idSeparatorPosition = leaf.find_first_of(
"_");
513 if (idSeparatorPosition == std::string::npos)
continue;
514 const std::string
id = leaf.substr(0, idSeparatorPosition);
515 if (
id.empty())
continue;
516 const std::string name = leaf.substr(idSeparatorPosition + 1);
517 AddMod(
id, filename, name);
523 this->parent = parent;
525 if (entry !=
nullptr)
526 items.emplace_back(std::move(std::make_unique<ModInfo>(entry)));
535 for (
auto &mod : items)
536 if (mod->modID == modID)
return;
537 items.emplace_back(std::move(std::make_unique<ModInfo>(modID, name)));
555 return progressDialog;
558 void C4StartupModsDownloader::CancelRequest()
560 for (
auto & mod : items)
561 mod->CancelRequest();
564 if (postMetadataClient.get())
567 postMetadataClient.reset();
571 progressDialog->
Close(
true);
572 delete progressDialog;
573 progressDialog =
nullptr;
575 progressCallback =
nullptr;
576 metadataQueriedForModIdx = -1;
583 assert(!items.empty());
584 bool hasFile =
false;
585 for (
auto & item : items)
586 if (!item->files.empty()) hasFile =
true;
594 progressCallback = std::bind(&C4StartupModsDownloader::ExecuteCheckDownloadProgress,
this);
602 C4StartupModsDownloader::ModInfo::ModInfo(std::string modID, std::string name) : ModInfo()
607 this->hasOnlyIncompleteInformation =
true;
610 void C4StartupModsDownloader::ModInfo::FromXMLData(
const ModXMLData &xmlData)
614 name = xmlData.
title;
620 for (
const auto & fileInfo : xmlData.
files)
622 files.emplace_back(ModInfo::FileInfo{ fileInfo.handle, fileInfo.name, fileInfo.size, fileInfo.sha1 });
623 requiredFilenames.insert(fileInfo.name);
627 void C4StartupModsDownloader::ModInfo::Clear()
631 requiredFilenames.clear();
632 downloadedBytes = totalBytes = 0;
633 delete originalXMLNode;
634 originalXMLNode =
nullptr;
637 void C4StartupModsDownloader::ModInfo::CancelRequest()
639 if (!postClient.get())
return;
644 std::string C4StartupModsDownloader::ModInfo::GetPath()
650 void C4StartupModsDownloader::ModInfo::CheckProgress()
653 if (HasError())
return;
656 if (successful)
return;
658 if (postClient.get() ==
nullptr)
660 postClient = std::make_unique<C4HTTPClient>();
662 if (!postClient->Init() || !postClient->SetServer((C4StartupModsDlg::GetBaseServerURL() +
"files/" + files.back().handle).c_str()))
667 postClient->SetExpectedResponseType(C4HTTPClient::ResponseType::NoPreference);
672 postClient->Query(
nullptr,
true);
677 downloadedBytes = postClient->getDownloadedSize() + totalSuccesfullyDownloadedBytes;
678 totalBytes = totalSuccesfullyDownloadedBytes;
679 for (
const auto &file : files)
680 totalBytes += file.size;
682 if (!postClient->isBusy())
684 if (!postClient->isSuccess())
686 errorMessage = std::string(
LoadResStr(
"IDS_MODS_NOINSTALL_CONNECTIONFAIL")) + postClient->GetError();
692 const std::string path = GetPath();
695 errorMessage =
LoadResStr(
"IDS_MODS_NOINSTALL_CREATEDIR");
700 std::ofstream os(path +
DirectorySeparator + files.back().name, std::iostream::out | std::iostream::binary);
703 errorMessage =
LoadResStr(
"IDS_MODS_NOINSTALL_CREATEFILE");
708 const auto newDownloadedBytes = postClient->getDownloadedSize();;
709 totalSuccesfullyDownloadedBytes += newDownloadedBytes;
710 os.write(
static_cast<const char*
>(postClient->getResultBin().getData()), newDownloadedBytes);
719 FILE *metadata = std::fopen((path +
DirectorySeparator +
"resource.xml").c_str(),
"w");
720 if (metadata !=
nullptr)
722 originalXMLNode->Print(metadata, 0);
723 std::fclose(metadata);
730 const std::string filename(*iter);
734 const std::string::size_type typeIndex = filename.rfind(
".");
735 if (typeIndex == std::string::npos)
continue;
736 const std::string ending(filename.substr(typeIndex + 1));
737 if (ending !=
"ocd" && ending !=
"ocf" && ending !=
"ocs")
continue;
740 std::string leaf(filename);
741 if (leafIndex != std::string::npos)
742 leaf = filename.substr(leafIndex + 1);
743 if (requiredFilenames.count(leaf) > 0)
continue;
753 void C4StartupModsDownloader::ExecuteCheckDownloadProgress()
756 if (progressDialog ==
nullptr)
return;
765 size_t downloadedBytes{ 0 }, totalBytes{ 0 };
767 bool anyNotFinished =
false;
769 for (
auto & mod : items)
771 mod->CheckProgress();
772 size_t downloaded, total;
773 std::tie(downloaded, total) = mod->GetProgress();
775 downloadedBytes += downloaded;
778 if (mod->IsBusy() || (!mod->HasError() && mod->HasFilesRemaining()))
780 anyNotFinished =
true;
785 progressDialog->
SetProgress(100 * downloadedBytes / totalBytes);
791 std::string errorMessage;
792 for (
auto & mod : items)
794 if (mod->WasSuccessful())
796 parent->modsDiscovery.
AddMod(mod->modID, mod->GetPath(), mod->name);
799 const std::string modError = mod->GetErrorMessage();
800 if (!modError.empty())
801 errorMessage +=
"|" + modError;
806 if (!errorMessage.empty())
814 void C4StartupModsDownloader::ExecuteMetadataUpdate()
816 if (!progressDialog)
return;
825 if (!postMetadataClient)
828 for (
size_t i =
static_cast<size_t>(metadataQueriedForModIdx + 1); i < items.size(); ++i)
830 auto &mod = items[i];
831 if (!mod->RequiresMetadataUpdate() || mod->HasError())
continue;
834 progressMessage.
Format(
LoadResStr(
"IDS_MODS_INSTALL_UPDATEMETADATA_FOR"), mod->name.c_str());
837 postMetadataClient = std::make_unique<C4HTTPClient>();
839 if (!postMetadataClient->Init() || !postMetadataClient->SetServer((C4StartupModsDlg::GetBaseServerURL() +
"uploads/" + mod->modID).c_str()))
841 mod->SetError(
LoadResStr(
"IDS_MODS_NOINSTALL_UPDATEMETADATAFAILED"));
842 postMetadataClient.reset();
845 postMetadataClient->SetExpectedResponseType(C4HTTPClient::ResponseType::XML);
848 postMetadataClient->Query(
nullptr,
false);
850 metadataQueriedForModIdx = i;
855 progressCallback = std::bind(&C4StartupModsDownloader::ExecutePreRequestChecks,
this);
860 assert(metadataQueriedForModIdx >= 0);
861 assert(metadataQueriedForModIdx < items.size());
862 auto &mod = items[metadataQueriedForModIdx];
864 if (!postMetadataClient->isBusy())
866 if (!postMetadataClient->isSuccess())
868 Log(postMetadataClient->GetError());
871 postMetadataClient.reset();
873 mod->SetError(
LoadResStr(
"IDS_MODS_NOINSTALL_UPDATEMETADATAFAILED"));
877 TiXmlDocument xmlDocument;
878 xmlDocument.Parse(postMetadataClient->getResultString());
879 std::cout << postMetadataClient->getResultString();
881 if (xmlDocument.Error())
883 Log(xmlDocument.ErrorDesc());
888 const char * resourceElementName =
"root";
889 const TiXmlElement *root = xmlDocument.RootElement();
890 assert(strcmp(root->Value(), resourceElementName) == 0);
896 if (modXMLData.id.empty())
898 mod->SetError(
LoadResStr(
"IDS_MODS_NOINSTALL_UPDATEMETADATAFAILED"));
901 postMetadataClient.reset();
907 for (;foundIdx < items.size(); ++foundIdx)
909 auto &mod = items[foundIdx];
910 if (mod->modID != modXMLData.id)
continue;
911 mod->FromXMLData(modXMLData);
915 progressDialog->
SetProgress(100 * foundIdx / items.size());
918 if (foundIdx == items.size())
920 Log((std::string(
"Answer for ") + modXMLData.id +
" was not requested.").c_str());
929 const std::unique_ptr<C4StartupModsDownloader::ModInfo> &mod = items[foundIdx];
931 for (
const std::string &dependency : mod->dependencies)
936 postMetadataClient.reset();
942 progressCallback = std::bind(&C4StartupModsDownloader::ExecutePreRequestChecks,
this);
945 void C4StartupModsDownloader::ExecutePreRequestChecks()
948 progressCallback =
nullptr;
951 for (
auto &mod : items)
953 if (!mod->RequiresMetadataUpdate() || mod->HasError())
continue;
954 progressCallback = std::bind(&C4StartupModsDownloader::ExecuteMetadataUpdate,
this);
962 bool anyModNeedsCheck =
false;
963 for (
auto &mod : items)
965 if (!mod->localDiscoveryCheck.needsCheck)
continue;
967 const bool modInstalled = mod->localDiscoveryCheck.installed = parent->modsDiscovery.
IsModInstalled(mod->modID);
968 mod->localDiscoveryCheck.basePath = modInstalled ? parent->modsDiscovery.
GetModInformation(mod->modID).
path :
"";
969 mod->localDiscoveryCheck.needsCheck =
false;
974 for (
auto file : mod->files)
976 if (file.sha1.empty())
continue;
977 mod->localDiscoveryCheck.needsCheck = anyModNeedsCheck =
true;
981 if (mod->localDiscoveryCheck.needsCheck)
983 mod->localDiscoveryCheck.Start();
988 if (anyModNeedsCheck)
990 progressCallback = std::bind(&C4StartupModsDownloader::ExecuteWaitForChecksums,
this);
998 ExecuteRequestConfirmation();
1002 void C4StartupModsDownloader::ExecuteWaitForChecksums()
1004 for (
auto &mod : items)
1006 if (mod->localDiscoveryCheck.needsCheck)
1009 progressCallback = std::bind(&C4StartupModsDownloader::ExecuteRequestConfirmation,
this);
1012 void C4StartupModsDownloader::ExecuteRequestConfirmation()
1014 progressCallback =
nullptr;
1017 size_t totalSize{ 0 };
1018 bool atLeastOneFileExisted =
false;
1019 std::string allMissingModNames;
1020 std::string errorMessages;
1022 for (
auto &mod : items)
1024 if (mod->localDiscoveryCheck.atLeastOneFileExisted)
1025 atLeastOneFileExisted =
true;
1026 if (!mod->files.empty())
1028 allMissingModNames += (allMissingModNames.empty() ?
"\"" :
", \"") + mod->name +
"\"";
1029 for (
auto file : mod->files)
1031 totalSize += file.size;
1034 if (mod->HasError())
1035 errorMessages += mod->GetErrorMessage() +
"\n";
1046 if (!errorMessages.empty())
1047 errorMessages +=
"\n";
1048 if (atLeastOneFileExisted)
1050 errorMessages +=
LoadResStr(
"IDS_MODS_NOINSTALL_ALREADYINSTALLED");
1054 errorMessages +=
LoadResStr(
"IDS_MODS_NOINSTALL_NODATA");
1061 std::string filesizeString;
1062 const size_t totalSizeMB = (totalSize / 1000 + 500) / 1000;
1063 if (totalSizeMB == 0)
1065 filesizeString =
"< 1 MB";
1069 filesizeString = std::string(
"~ ") + std::to_string(totalSizeMB) +
" MB";
1073 confirmationMessage.
Format(
LoadResStr(
"IDS_MODS_INSTALL_CONFIRM"), allMissingModNames.c_str(), filesizeString.c_str());
1083 for (
auto fileIterator =
mod.files.begin(); fileIterator !=
mod.files.end();)
1085 auto &file = *fileIterator;
1086 bool fileExists =
false;
1089 if (!file.sha1.empty())
1091 const std::string &hashString = file.sha1;
1099 const size_t byteLen = 2;
1101 for (
size_t offset = 0; offset < hashString.size(); offset += byteLen, index += 1)
1104 const size_t hashIndex = (index / 4 * 4) + (3 - (index % 4));
1105 const BYTE &
byte = hash[hashIndex];
1107 const std::string byteStr = hashString.substr(offset, byteLen);
1108 unsigned char byteStrValue =
static_cast<unsigned char> (std::stoi(byteStr,
nullptr, 16));
1110 if (byteStrValue !=
byte)
1121 fileIterator =
mod.files.erase(fileIterator);
1152 int32_t iButtonWidth,iCaptionFontHgt, iSideSize = std::max<int32_t>(
GetBounds().Wdt/6, iIconSize);
1158 int32_t iButtonAreaWdt = caButtonArea.
GetWidth()*7/8;
1159 iButtonWidth = std::min<int32_t>(iButtonWidth, (iButtonAreaWdt - 8 * iButtonIndent)/4);
1160 iButtonIndent = (iButtonAreaWdt - 4 * iButtonWidth) / 8;
1179 int32_t maxSortLabelWidth = 0;
1183 {
"title",
"IDS_MODS_SORT_NAME_UP",
"IDS_MODS_SORT_NAME_DOWN" },
1184 {
"updatedAt",
"IDS_MODS_SORT_DATE_DOWN",
"IDS_MODS_SORT_DATE_UP" },
1187 for (
auto &option : sortingOptions)
1189 int32_t iSortWdt = 100, iSortHgt;
1190 for (
auto label : { &SortingOption::titleAsc, &SortingOption::titleDesc })
1195 if (iSortWdt > maxSortLabelWidth)
1196 maxSortLabelWidth = iSortWdt;
1202 const char *szSearchLblText =
LoadResStr(
"IDS_NET_MSSEARCH");
1203 int32_t iSearchWdt=100, iSearchHgt;
1207 const char *szSearchTip =
LoadResStr(
"IDS_MODS_SEARCH_DESC");
1218 pSortComboBox->SetText(
LoadResStr(
"IDS_MODS_SORT"));
1245 const auto showInstalledHint =
LoadResStr(
"IDS_MODS_SHOWINSTALLED_HINT");
1258 auto addCheckbox = [&pUseFont, &caConfigArea,
this] (
C4GUI::CheckBox**checkbox,
const char *szText,
const char *szTooltip,
const C4GUI::Icons icon)
1260 int iWdt = 150, iHgt = 12;
1262 *checkbox =
new C4GUI::CheckBox(caConfigArea.GetFromTop(iHgt, -1), szText,
true);
1263 (*checkbox)->SetToolTip(szTooltip);
1265 const auto& checkbox_bounds = (*checkbox)->GetBounds();
1267 C4Rect icon_rect(checkbox_bounds.GetRight(), checkbox_bounds.GetTop(), line_height, line_height);
1268 auto icon_element =
new C4GUI::Icon(icon_rect, icon);
1288 std::string C4StartupModsDlg::GetBaseServerURL()
1291 assert(!base.empty());
1292 if (base.empty())
return base;
1293 if (base.back() !=
'/')
1298 std::string C4StartupModsDlg::GetOpenClonkVersionStringTag()
1300 return "openclonk-" + std::to_string(C4XVER1);
1319 Base::DrawElement(cgo);
1344 return pGameSelList;
1347 void C4StartupModsDlg::QueryModList(
bool loadNextPage)
1353 if (loadNextPage && pGameSelList->
GetLast() !=
nullptr)
1363 infoEntry->MakeInfoEntry();
1367 std::string searchQueryPostfix(
"?");
1368 if (pSearchFieldEdt->
GetText())
1370 std::string searchText(pSearchFieldEdt->
GetText());
1371 if (searchText.size() > 0)
1374 searchText = std::regex_replace(searchText, std::regex(
"\""),
"\\\"");
1375 searchText = std::regex_replace(searchText, std::regex(
"[ ]+"),
"%20");
1376 searchQueryPostfix +=
"q=%22" + searchText +
"%22&";
1380 std::vector<std::string> tagFilters;
1381 tagFilters.reserve(4);
1383 if (filters.showCompatible->GetChecked())
1385 static const std::string versionTag = GetOpenClonkVersionStringTag();
1386 tagFilters.push_back(versionTag);
1388 if (filters.showPlayable->GetChecked())
1390 tagFilters.push_back(
".scenario");
1392 if (!tagFilters.empty())
1394 std::ostringstream os;
1395 for (
size_t i = 0; i < tagFilters.size(); ++i)
1396 os << ((i == 0) ?
"tags=" :
",") << tagFilters[i];
1397 searchQueryPostfix += os.str() +
"&";
1401 if (!sortKeySuffix.empty())
1403 searchQueryPostfix +=
"sort=" + sortKeySuffix +
"&";
1407 const int requestedOffset = loadNextPage ? pageInfo.currentlySkipped + pageInfo.maxResultsPerQuery: 0;
1408 searchQueryPostfix +=
"limit=" + std::to_string(pageInfo.maxResultsPerQuery) +
"&skip=" + std::to_string(requestedOffset) +
"&";
1413 queryWasSuccessful =
false;
1414 postClient = std::make_unique<C4HTTPClient>();
1416 if (!postClient->Init() || !postClient->SetServer((C4StartupModsDlg::GetBaseServerURL() +
"uploads" + searchQueryPostfix).c_str()))
1418 infoEntry->OnError(std::string(
LoadResStr(
"IDS_MODS_INVALID_SERVER")) + C4StartupModsDlg::GetBaseServerURL());
1421 queryWasSuccessful =
true;
1424 postClient->SetExpectedResponseType(C4HTTPClient::ResponseType::XML);
1429 postClient->Query(
nullptr,
false);
1432 void C4StartupModsDlg::CancelRequest()
1434 if (!postClient.get())
return;
1437 lastQueryEndTime = time(
nullptr);
1440 void C4StartupModsDlg::ClearList()
1443 while ((pElem = pNextElem))
1451 void C4StartupModsDlg::AddToList(std::vector<TiXmlElementLoaderInfo> elements,
ModXMLData::Source source)
1454 for (
const auto e : elements)
1457 pEntry->
FromXML(e.element, source, e.id, e.name);
1474 UpdateList(
false,
true);
1479 void C4StartupModsDlg::UpdateList(
bool fGotReference,
bool onlyWithLocalFiles)
1481 if (onlyWithLocalFiles)
1485 if (postClient.get() !=
nullptr)
1492 auto lock = std::move(modsDiscovery.
Lock());
1495 std::vector<TiXmlElementLoaderInfo> elements;
1496 for (
const auto & modData: installedMods)
1498 const auto &mod = modData.second;
1499 TiXmlElement *metadataXMLElement{
nullptr };
1503 if (metadata.good())
1505 std::stringstream stream;
1506 stream << metadata.rdbuf();
1508 TiXmlDocument xmlDocument;
1509 xmlDocument.Parse(stream.str().c_str());
1510 if (!xmlDocument.Error())
1512 const TiXmlElement *root = xmlDocument.RootElement();
1513 metadataXMLElement =
static_cast<TiXmlElement*
>(root->Clone());
1516 elements.emplace_back(metadataXMLElement, mod.
id, mod.
name);
1520 for (
auto & e : elements)
1523 if (elements.empty())
1530 queryWasSuccessful =
true;
1534 if (postClient.get() !=
nullptr)
1537 if (!postClient->isBusy())
1541 assert(infoEntry !=
nullptr);
1543 if (!postClient->isSuccess())
1545 Log(postClient->GetError());
1546 infoEntry->
OnError(postClient->GetError());
1551 queryWasSuccessful =
true;
1553 TiXmlDocument xmlDocument;
1554 xmlDocument.Parse(postClient->getResultString());
1555 std::cout << postClient->getResultString() << std::endl;
1557 if (xmlDocument.Error())
1559 Log(xmlDocument.ErrorDesc());
1561 infoEntry->
OnError(xmlDocument.ErrorDesc());
1564 const char * rootElementName =
"root";
1565 const TiXmlElement *root = xmlDocument.RootElement();
1566 assert(strcmp(root->Value(), rootElementName) == 0);
1569 pageInfo.totalResults = pageInfo.currentlySkipped = 0;
1570 const TiXmlElement *meta = root->FirstChildElement(
"meta");
1571 if (meta !=
nullptr)
1580 const TiXmlElement* resources = root->FirstChildElement(
"resources");
1581 const char* resourceElementName =
"item";
1582 std::vector<TiXmlElementLoaderInfo> elements;
1583 for (
const TiXmlElement* e = resources->FirstChildElement(resourceElementName); e != NULL; e = e->NextSiblingElement(resourceElementName))
1586 if (e->FirstChild() ==
nullptr)
1588 elements.push_back(e);
1593 if (elements.empty())
1595 else if (pageInfo.getCurrentPage() < pageInfo.getTotalPages())
1597 infoEntry->
ShowPageInfo(pageInfo.getCurrentPage(), pageInfo.getTotalPages(), pageInfo.totalResults);
1609 if (!queryWasSuccessful && lastQueryEndTime + QueryRetryTimeout < time(
nullptr))
1619 if (requiredSyncWithDiscovery)
1621 requiredSyncWithDiscovery =
false;
1624 while ((pElem = pNextElem))
1645 void C4StartupModsDlg::UpdateSelection()
1651 if (selected !=
nullptr && !selected->
IsInfoEntry())
1674 void C4StartupModsDlg::CheckUpdateAll()
1678 auto lock = std::move(modsDiscovery.
Lock());
1682 if (allInstalledMods.empty())
1688 if (downloader.get() !=
nullptr)
1690 downloader = std::make_unique<C4StartupModsDownloader>(
this,
nullptr);
1692 for (
const auto & pair : allInstalledMods)
1694 const std::string &modID = pair.first;
1695 const std::string &name = pair.second.name;
1697 downloader->AddModToQueue(modID, name);
1700 downloader->RequestConfirmation();
1703 void C4StartupModsDlg::CheckRemoveMod()
1706 if (selected ==
nullptr)
return;
1716 void C4StartupModsDlg::OnConfirmRemoveMod(
C4GUI::Element *element)
1719 if (selected ==
nullptr)
return;
1729 std::string errorMessage;
1731 auto dw = GetLastError();
1732 LPSTR messageBuffer =
nullptr;
1733 size_t size = FormatMessageA(
1734 FORMAT_MESSAGE_ALLOCATE_BUFFER |
1735 FORMAT_MESSAGE_FROM_SYSTEM |
1736 FORMAT_MESSAGE_IGNORE_INSERTS,
1739 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
1740 (LPSTR)&messageBuffer,
1743 errorMessage = std::string(
"||") + std::string(messageBuffer, size);
1777 if (elem->IsInfoEntry())
1779 if (postClient.get() !=
nullptr)
return false;
1781 if (pageInfo.getCurrentPage() >= pageInfo.getTotalPages())
return false;
1785 if (elem->GetID().empty())
return false;
1787 if (downloader.get() !=
nullptr)
1789 downloader = std::make_unique<C4StartupModsDownloader>(
this, elem);
1790 downloader->RequestConfirmation();
1804 if (!postClient.get())
1829 int32_t counter = 0;
1830 for (
auto & option : sortingOptions)
1833 pFiller->
AddEntry(option.titleAsc, counter++);
1834 pFiller->
AddEntry(option.titleDesc, counter++);
1840 const size_t selected = idNewSelection / 2;
1841 const bool descending = idNewSelection % 2 == 1;
1842 const std::string newSortKeySuffix = std::string(descending ?
"-" :
"") + sortingOptions[selected].key;
1843 if (newSortKeySuffix == sortKeySuffix)
return true;
1844 sortKeySuffix = newSortKeySuffix;
1846 const char *sortLabel = descending ? sortingOptions[selected].titleDesc : sortingOptions[selected].titleAsc;
1847 pForCombo->
SetText(sortLabel);
1855 std::string modID(toScreen);
1856 if (modID.empty())
return false;
1858 if (downloader.get() !=
nullptr)
1861 downloader = std::make_unique<C4StartupModsDownloader>(
this,
nullptr);
1863 std::stringstream ss(modID);
1864 while (std::getline(ss, modID,
'-'))
1866 downloader->AddModToQueue(modID,
"???");
1868 downloader->RequestConfirmation();
C4Application Application
C4GraphicsResource GraphicsResource
#define C4GUI_CaptionFontClr
#define C4GUI_MessageFontClr
#define C4GUI_Caption2FontClr
const char * LoadResStr(const char *id)
bool Log(const char *szMessage)
std::string getSafeStringValue(const TiXmlElement *xml, const char *childName, std::string fallback="", bool isAttribute=false)
bool GetFileSHA1(const char *szFilename, BYTE *pSHA1)
#define SHA_DIGEST_LENGTH
bool IsValidUtf8(const char *text, int length)
uint32_t GetNextUTF8Character(const char **pszString)
bool DirectoryExists(const char *szFilename)
bool EraseDirectory(const char *szDirName)
bool CreatePath(const std::string &path)
bool EraseFile(const char *szFileName)
C4InteractiveThread InteractiveThread
char UserDataPath[CFG_MaxString+1]
const char * GetModDatabaseServerAddress()
std::vector< C4KeyCodeEx > CodeList
static bool GetStandardCheckBoxSize(int *piWdt, int *piHgt, const char *szForCaptionText, CStdFont *pUseFont)
void AddEntry(const char *szText, int32_t id)
void SetText(const char *szToText)
bool GetFromLeft(int32_t iWdt, int32_t iHgt, C4Rect &rcOut)
bool GetCentered(int32_t iWdt, int32_t iHgt, C4Rect &rcOut)
int32_t GetHeight() const
bool GetFromRight(int32_t iWdt, int32_t iHgt, C4Rect &rcOut)
bool GetFromTop(int32_t iHgt, int32_t iWdt, C4Rect &rcOut)
void GetAll(C4Rect &rcOut)
bool GetFromBottom(int32_t iHgt, int32_t iWdt, C4Rect &rcOut)
void AddElement(Element *pChild)
void SetVisibility(bool fToValue) override
void SetFocus(Control *pCtrl, bool fByMouse)
void SetDelOnClose(bool fToVal=true)
bool IsActive(bool fForKeyboard)
void UpdateSize() override
void SetTitle(const char *szToTitle, bool fShowCloseButton=true)
virtual Screen * GetScreen()
Element * GetNext() const
C4Rect GetContainedClientRect()
virtual void UpdateSize()
void SetBounds(const C4Rect &rcNewBound)
void SetToolTip(const char *szNewTooltip, bool is_immediate=false)
const char * GetToolTip()
void SetIcon(Icons icoNewIconIndex)
void SetText(const char *szToText, bool fAllowHotkey=true)
void SetAutosize(bool fToVal)
void SetColor(DWORD dwToClr, bool fMakeReadableOnBlack=true)
Element * GetSelectedItem()
bool InsertElement(Element *pChild, Element *pInsertBefore, int32_t iIndent=0)
void SetSelectionDblClickFn(BaseCallbackHandler *pToHandler)
void SetSelectionChangeCallbackFn(BaseCallbackHandler *pToHandler)
bool AddElement(Element *pChild, int32_t iIndent=0)
void UpdateElementPositions()
void SetDecoration(bool fDrawBG, ScrollBarFacets *pToGfx, bool fAutoScroll, bool fDrawBorder=false)
void RemoveElement(Element *pChild) override
void SetFacet(const C4Facet &fct)
void SetAnimated(bool fEnabled, int iDelay)
const C4FacetSurface & GetFacet() const
void SetProgress(int32_t iToProgress)
void SetMessage(const char *szMessage)
bool ShowRemoveDlg(Dialog *pDlg)
bool ShowMessageModal(const char *szMessage, const char *szCaption, DWORD dwButtons, Icons icoIcon, int32_t *piConfigDontShowAgainSetting=nullptr)
Sheet * AddSheet(const char *szTitle, int32_t icoTitle=Ico_None)
void SetDrawDecoration(bool fToVal)
void SetSheetMargin(int32_t iMargin)
C4Rect & GetClientRect() override
static int32_t GetDefaultHeight(CStdFont *pUseFont=nullptr)
void RemoveProc(StdSchedulerProc *pProc)
void ClearCallback(C4InteractiveEventType eEvent, Callback *pnNetworkCallback)
bool AddProc(StdSchedulerProc *pProc)
void SetCallback(C4InteractiveEventType eEvent, Callback *pnNetworkCallback)
void Set(int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt)
class C4StartupDlg * SwitchDialog(DialogID eToDlg, bool fFade=true, const char *szSubDialog=nullptr)
C4StartupGraphics Graphics
virtual bool SetSubscreen(const char *toScreen) override
C4GUI::Control * GetDlgModeFocusControl()
void OnUpdateAllBtn(C4GUI::Control *btn)
virtual void DrawElement(C4TargetFacet &cgo)
virtual C4GUI::Control * GetDefaultControl()
void OnInstallModBtn(C4GUI::Control *btn)
void OnSelChange(class C4GUI::Element *pEl)
void OnSelDblClick(class C4GUI::Element *pEl)
void QueueSyncWithDiscovery()
bool OnSortComboSelChange(C4GUI::ComboBox *pForCombo, int32_t idNewSelection)
virtual void OnClosed(bool fOK)
friend class C4StartupModsListEntry
void OnShowInstalledBtn(C4GUI::Control *btn)
void OnSortComboFill(C4GUI::ComboBox_FillCB *pFiller)
C4GUI::Edit::InputResult OnSearchFieldEnter(C4GUI::Edit *edt, bool fPasting, bool fPastingMore)
void OnSearchOnlineBtn(C4GUI::Control *btn)
void OnBackBtn(C4GUI::Control *btn)
void OnUninstallModBtn(C4GUI::Control *btn)
C4StartupModsDownloader(C4StartupModsDlg *parent, const C4StartupModsListEntry *entry)
~C4StartupModsDownloader()
void OnConfirmInstallation(C4GUI::Element *element)
void AddModToQueue(std::string modID, std::string name)
void RequestConfirmation()
void UpdateInstalledState(C4StartupModsLocalModDiscovery::ModsInfo *modInfo)
std::string GetTitle() const
const ModXMLData & GetModXMLData() const
void ShowPageInfo(int page, int totalPages, int totalResults)
std::string GetID() const
~C4StartupModsListEntry()
virtual void DrawElement(C4TargetFacet &cgo)
void FromXML(const TiXmlElement *xml, ModXMLData::Source source, std::string fallbackID="", std::string fallbackName="")
void SetVisibility(bool fToValue)
C4StartupModsListEntry(C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBefore, class C4StartupModsDlg *pModsDlg)
void OnError(std::string message)
void RemoveMod(const std::string &id)
bool IsDiscoveryFinished() const
const std::map< std::string, ModsInfo > & GetAllModInformation()
const bool IsModInstalled(const std::string &id)
void WaitForDiscoveryFinished()
ModsInfo & AddMod(const std::string &id, const std::string &path, const std::string &name)
ModsInfo GetModInformation(const std::string &id)
int GetLineHeight() const
std::tuple< std::string, int > BreakMessage(const char *szMsg, int iWdt, bool fCheckMarkup, float fZoom=1.0f)
bool GetTextExtent(const char *szText, int32_t &rsx, int32_t &rsy, bool fCheckMarkup=true)
void Remove(StdSchedulerProc *pProc)
void Add(StdSchedulerProc *pProc)
const char * getData() const
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
bool atLeastOneFileExisted
std::string longDescription
std::vector< std::string > dependencies
std::vector< FileInfo > files
TiXmlNode * originalXMLElement
std::vector< std::string > tags
bool requiresUpdate() const
ModXMLData(const TiXmlElement *xml, Source source=Source::Unknown)