From 06ce71087ea9239fc55f6cc32b8e33bee222b86b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 13 Nov 2024 17:25:59 +0000 Subject: [PATCH] Restored mod list display functionality in launcher --- launcher/modManager/cmodlistview_moc.cpp | 69 ++++++++++++------- launcher/modManager/modstate.cpp | 8 +-- launcher/modManager/modstatecontroller.cpp | 4 +- launcher/modManager/modstatecontroller.h | 2 +- launcher/modManager/modstateitemmodel_moc.cpp | 7 +- launcher/modManager/modstateitemmodel_moc.h | 4 +- launcher/modManager/modstatemodel.cpp | 21 ++++-- launcher/modManager/modstatemodel.h | 4 +- lib/modding/ModDescription.cpp | 31 +++++---- lib/modding/ModDescription.h | 7 +- lib/modding/ModManager.cpp | 35 ++++++++-- lib/modding/ModManager.h | 8 ++- vcmiqt/jsonutils.cpp | 8 +-- vcmiqt/jsonutils.h | 4 +- 14 files changed, 137 insertions(+), 75 deletions(-) diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 361c04ca6..570df8072 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -37,8 +37,14 @@ void CModListView::setupModModel() { + static const QString repositoryCachePath = CLauncherDirs::downloadsPath() + "/repositoryCache.json"; + const auto &cachedRepositoryData = JsonUtils::jsonFromFile(repositoryCachePath); + modStateModel = std::make_shared(); - modModel = new ModStateItemModel(this); + if (!cachedRepositoryData.isNull()) + modStateModel->appendRepositories(cachedRepositoryData); + + modModel = new ModStateItemModel(modStateModel, this); manager = std::make_unique(modStateModel); } @@ -248,6 +254,14 @@ QStringList CModListView::getModNames(QStringList input) for(const auto & modID : input) { + // Missing mod - use mod ID + // TODO: for submods show parent ID instead + if (!modStateModel->isModExists(modID)) + { + result += modID; + continue; + } + auto mod = modStateModel->getMod(modID); QString displayName = mod.getName(); @@ -298,10 +312,10 @@ QString CModListView::genModInfoText(ModState & mod) result += replaceIfNotEmpty(mod.getAuthors(), lineTemplate.arg(tr("Authors"))); - if(mod.getLicenseName().isEmpty()) + if(!mod.getLicenseName().isEmpty()) result += urlTemplate.arg(tr("License")).arg(mod.getLicenseUrl()).arg(mod.getLicenseName()); - if(mod.getContact().isEmpty()) + if(!mod.getContact().isEmpty()) result += urlTemplate.arg(tr("Contact")).arg(mod.getContact()).arg(mod.getContact()); //compatibility info @@ -357,12 +371,12 @@ QString CModListView::genModInfoText(ModState & mod) QString notes; - notes += replaceIfNotEmpty(getModNames(findInvalidDependencies(mod.getName())), listTemplate.arg(unknownDeps)); - notes += replaceIfNotEmpty(getModNames(findBlockingMods(mod.getName())), listTemplate.arg(blockingMods)); + notes += replaceIfNotEmpty(getModNames(findInvalidDependencies(mod.getID())), listTemplate.arg(unknownDeps)); + notes += replaceIfNotEmpty(getModNames(findBlockingMods(mod.getID())), listTemplate.arg(blockingMods)); if(mod.isEnabled()) - notes += replaceIfNotEmpty(getModNames(findDependentMods(mod.getName(), true)), listTemplate.arg(hasActiveDependentMods)); + notes += replaceIfNotEmpty(getModNames(findDependentMods(mod.getID(), true)), listTemplate.arg(hasActiveDependentMods)); if(mod.isInstalled()) - notes += replaceIfNotEmpty(getModNames(findDependentMods(mod.getName(), false)), listTemplate.arg(hasDependentMods)); + notes += replaceIfNotEmpty(getModNames(findDependentMods(mod.getID(), false)), listTemplate.arg(hasDependentMods)); if(mod.isSubmod()) notes += noteTemplate.arg(thisIsSubmod); @@ -463,7 +477,7 @@ QStringList CModListView::findInvalidDependencies(QString mod) QStringList ret; for(QString requirement : modStateModel->getMod(mod).getDependencies()) { - if(!modStateModel->isModExists(requirement) && !modStateModel->isModExists(modStateModel->getMod(requirement).getTopParentID())) + if(!modStateModel->isModExists(requirement)) ret += requirement; } return ret; @@ -776,7 +790,7 @@ void CModListView::installFiles(QStringList files) QStringList maps; QStringList images; QStringList exe; - QVector repositories; + JsonNode repository; // TODO: some better way to separate zip's with mods and downloaded repository files for(QString filename : files) @@ -790,33 +804,40 @@ void CModListView::installFiles(QStringList files) else if(filename.endsWith(".json", Qt::CaseInsensitive)) { //download and merge additional files - auto repoData = JsonUtils::JsonFromFile(filename).toMap(); - if(repoData.value("name").isNull()) + const auto &repoData = JsonUtils::jsonFromFile(filename); + if(repoData["name"].isNull()) { - for(const auto & key : repoData.keys()) + // This is main repository index. Download all referenced mods + for(const auto & [modName, modJson] : repoData.Struct()) { - auto modjson = repoData[key].toMap().value("mod"); - if(!modjson.isNull()) - { - downloadFile(key + ".json", modjson.toString(), tr("mods repository index")); - } + auto modNameLower = boost::algorithm::to_lower_copy(modName); + auto modJsonUrl = modJson["mod"]; + if(!modJsonUrl.isNull()) + downloadFile(QString::fromStdString(modName + ".json"), QString::fromStdString(modJsonUrl.String()), tr("mods repository index")); + + repository[modNameLower] = repoData; } } else { - auto modn = QFileInfo(filename).baseName(); - QVariantMap temp; - temp[modn] = repoData; - repoData = temp; + // This is json of a single mod. Extract name of mod and add it to repo + auto modName = QFileInfo(filename).baseName().toStdString(); + auto modNameLower = boost::algorithm::to_lower_copy(modName); + repository[modNameLower] = repoData; } - repositories.push_back(repoData); } else if(filename.endsWith(".png", Qt::CaseInsensitive)) images.push_back(filename); } -// if (!repositories.empty()) -// manager->loadRepositories(repositories); + if (!repository.isNull()) + { + manager->appendRepositories(repository); + modModel->reloadRepositories(); + + static const QString repositoryCachePath = CLauncherDirs::downloadsPath() + "/repositoryCache.json"; + JsonUtils::jsonToFile(repositoryCachePath, modStateModel->getRepositoryData()); + } if(!mods.empty()) installMods(mods); diff --git a/launcher/modManager/modstate.cpp b/launcher/modManager/modstate.cpp index b73f36fe2..38b4987c8 100644 --- a/launcher/modManager/modstate.cpp +++ b/launcher/modManager/modstate.cpp @@ -120,7 +120,7 @@ QString ModState::getLocalSizeFormatted() const QString ModState::getAuthors() const { - return QString::fromStdString(impl.getValue("authors").String()); + return QString::fromStdString(impl.getValue("author").String()); } QString ModState::getContact() const @@ -130,7 +130,7 @@ QString ModState::getContact() const QString ModState::getLicenseUrl() const { - return QString::fromStdString(impl.getValue("licenseUrl").String()); + return QString::fromStdString(impl.getValue("licenseURL").String()); } QString ModState::getLicenseName() const @@ -173,7 +173,7 @@ bool ModState::isHidden() const if (isTranslation() && !isInstalled()) return impl.getBaseLanguage() == CGeneralTextHandler::getPreferredLanguage(); - return isCompatibility() || getID() == "vcmi"; + return isCompatibility() || getID() == "vcmi" || getID() == "core"; } bool ModState::isDisabled() const @@ -198,7 +198,7 @@ bool ModState::isInstalled() const bool ModState::isUpdateAvailable() const { - return getInstalledVersion() != getRepositoryVersion() && !getRepositoryVersion().isEmpty(); + return getInstalledVersion() != getRepositoryVersion() && !getRepositoryVersion().isEmpty() && !getInstalledVersion().isEmpty();; } bool ModState::isCompatible() const diff --git a/launcher/modManager/modstatecontroller.cpp b/launcher/modManager/modstatecontroller.cpp index c1f2fc194..ac7e6470d 100644 --- a/launcher/modManager/modstatecontroller.cpp +++ b/launcher/modManager/modstatecontroller.cpp @@ -71,9 +71,9 @@ ModStateController::ModStateController(std::shared_ptr modList) ModStateController::~ModStateController() = default; -void ModStateController::loadRepositories(QVector repomap) +void ModStateController::appendRepositories(const JsonNode & repomap) { - modList->setRepositories(repomap); + modList->appendRepositories(repomap); } //void ModStateController::loadMods() diff --git a/launcher/modManager/modstatecontroller.h b/launcher/modManager/modstatecontroller.h index 545678b3b..17c87a644 100644 --- a/launcher/modManager/modstatecontroller.h +++ b/launcher/modManager/modstatecontroller.h @@ -38,7 +38,7 @@ public: ModStateController(std::shared_ptr modList); ~ModStateController(); - void loadRepositories(QVector repositoriesList); + void appendRepositories(const JsonNode & repositoriesList); QStringList getErrors(); diff --git a/launcher/modManager/modstateitemmodel_moc.cpp b/launcher/modManager/modstateitemmodel_moc.cpp index 4a0d51787..de01671e0 100644 --- a/launcher/modManager/modstateitemmodel_moc.cpp +++ b/launcher/modManager/modstateitemmodel_moc.cpp @@ -25,8 +25,9 @@ static const QString iconEnabledSubmod = ":/icons/submod-enabled.png"; static const QString iconUpdate = ":/icons/mod-update.png"; } -ModStateItemModel::ModStateItemModel(QObject * parent) +ModStateItemModel::ModStateItemModel(std::shared_ptr model, QObject * parent) : QAbstractItemModel(parent) + , model(model) { } @@ -164,7 +165,7 @@ QVariant ModStateItemModel::data(const QModelIndex & index, int role) const case ModRoles::ValueRole: return getValue(mod, index.column()); case ModRoles::ModNameRole: - return mod.getName(); + return mod.getID(); } } return QVariant(); @@ -280,7 +281,7 @@ bool CModFilterModel::filterAcceptsRow(int source_row, const QModelIndex & sourc { QModelIndex index = base->index(source_row, 0, source_parent); QString modID = index.data(ModRoles::ModNameRole).toString(); - if (base->model->isModVisible(modID)) + if (base->model->getMod(modID).isHidden()) return false; if(filterMatchesThis(index)) diff --git a/launcher/modManager/modstateitemmodel_moc.h b/launcher/modManager/modstateitemmodel_moc.h index 5ce8bc3dc..9f7bad778 100644 --- a/launcher/modManager/modstateitemmodel_moc.h +++ b/launcher/modManager/modstateitemmodel_moc.h @@ -46,7 +46,7 @@ enum EModRoles }; } -class ModStateItemModel : public QAbstractItemModel +class ModStateItemModel : public QAbstractItemModel { friend class CModFilterModel; Q_OBJECT @@ -69,7 +69,7 @@ class ModStateItemModel : public QAbstractItemModel QVariant getIcon(const ModState & mod, int field) const; public: - explicit ModStateItemModel(QObject * parent = nullptr); + explicit ModStateItemModel(std::shared_ptr model, QObject * parent = nullptr); /// CModListContainer overrides void reloadRepositories(); diff --git a/launcher/modManager/modstatemodel.cpp b/launcher/modManager/modstatemodel.cpp index f93c6f0aa..fd9092f74 100644 --- a/launcher/modManager/modstatemodel.cpp +++ b/launcher/modManager/modstatemodel.cpp @@ -11,20 +11,31 @@ #include "modstatemodel.h" #include "../../lib/modding/ModManager.h" +#include "../../lib/json/JsonUtils.h" ModStateModel::ModStateModel() - :modManager(std::make_unique()) -{} + : repositoryData(std::make_unique()) + , modManager(std::make_unique()) +{ +} ModStateModel::~ModStateModel() = default; -void ModStateModel::setRepositories(QVector repositoriesList) +void ModStateModel::appendRepositories(const JsonNode & repositoriesList) { - //TODO + JsonUtils::mergeCopy(*repositoryData, repositoriesList); + + modManager = std::make_unique(*repositoryData); +} + +const JsonNode & ModStateModel::getRepositoryData() const +{ + return *repositoryData; } ModState ModStateModel::getMod(QString modName) const { + assert(modName.toLower() == modName); return ModState(modManager->getModDescription(modName.toStdString())); } @@ -39,7 +50,7 @@ QStringList stringListStdToQt(const Container & container) QStringList ModStateModel::getAllMods() const { - return stringListStdToQt(modManager->getActiveMods()); + return stringListStdToQt(modManager->getAllMods()); } QStringList ModStateModel::getSubmods(QString modName) const diff --git a/launcher/modManager/modstatemodel.h b/launcher/modManager/modstatemodel.h index 090f62703..368ee1147 100644 --- a/launcher/modManager/modstatemodel.h +++ b/launcher/modManager/modstatemodel.h @@ -20,13 +20,15 @@ VCMI_LIB_NAMESPACE_END /// Provides Qt-based interface to library class ModManager class ModStateModel { + std::unique_ptr repositoryData; std::unique_ptr modManager; public: ModStateModel(); ~ModStateModel(); - void setRepositories(QVector repositoriesList); + void appendRepositories(const JsonNode & repositoriesList); + const JsonNode & getRepositoryData() const; ModState getMod(QString modName) const; QStringList getAllMods() const; diff --git a/lib/modding/ModDescription.cpp b/lib/modding/ModDescription.cpp index 0330d6c20..98e4e4175 100644 --- a/lib/modding/ModDescription.cpp +++ b/lib/modding/ModDescription.cpp @@ -17,12 +17,13 @@ VCMI_LIB_NAMESPACE_BEGIN -ModDescription::ModDescription(const TModID & fullID, const JsonNode & config) +ModDescription::ModDescription(const TModID & fullID, const JsonNode & localConfig, const JsonNode & repositoryConfig) : identifier(fullID) - , localConfig(std::make_unique(config)) - , dependencies(loadModList(config["depends"])) - , softDependencies(loadModList(config["softDepends"])) - , conflicts(loadModList(config["conflicts"])) + , localConfig(std::make_unique(localConfig)) + , repositoryConfig(std::make_unique(repositoryConfig)) + , dependencies(loadModList(getValue("depends"))) + , softDependencies(loadModList(getValue("softDepends"))) + , conflicts(loadModList(getValue("conflicts"))) { if(getID() != "core") dependencies.insert("core"); @@ -72,17 +73,17 @@ const std::string & ModDescription::getBaseLanguage() const { static const std::string defaultLanguage = "english"; - return getLocalConfig()["language"].isString() ? getLocalConfig()["language"].String() : defaultLanguage; + return getValue("language").isString() ? getValue("language").String() : defaultLanguage; } const std::string & ModDescription::getName() const { - return getLocalConfig()["name"].String(); + return getValue("name").String(); } const JsonNode & ModDescription::getFilesystemConfig() const { - return getLocalConfig()["filesystem"]; + return getLocalValue("filesystem"); } const JsonNode & ModDescription::getLocalConfig() const @@ -92,7 +93,11 @@ const JsonNode & ModDescription::getLocalConfig() const const JsonNode & ModDescription::getValue(const std::string & keyName) const { - return getLocalConfig()[keyName]; + const JsonNode & localValue = getLocalValue(keyName); + if (localValue.isNull()) + return getRepositoryValue(keyName); + else + return getLocalValue(keyName); } const JsonNode & ModDescription::getLocalValue(const std::string & keyName) const @@ -107,7 +112,7 @@ const JsonNode & ModDescription::getRepositoryValue(const std::string & keyName) CModVersion ModDescription::getVersion() const { - return CModVersion::fromString(getLocalConfig()["version"].String()); + return CModVersion::fromString(getValue("version").String()); } ModVerificationInfo ModDescription::getVerificationInfo() const @@ -123,17 +128,17 @@ ModVerificationInfo ModDescription::getVerificationInfo() const bool ModDescription::isCompatibility() const { - return getLocalConfig()["modType"].String() == "Compatibility"; + return getValue("modType").String() == "Compatibility"; } bool ModDescription::isTranslation() const { - return getLocalConfig()["modType"].String() == "Translation"; + return getValue("modType").String() == "Translation"; } bool ModDescription::keepDisabled() const { - return getLocalConfig()["keepDisabled"].Bool(); + return getValue("keepDisabled").Bool(); } bool ModDescription::isInstalled() const diff --git a/lib/modding/ModDescription.h b/lib/modding/ModDescription.h index d771514e1..52aa574e2 100644 --- a/lib/modding/ModDescription.h +++ b/lib/modding/ModDescription.h @@ -21,18 +21,17 @@ using TModSet = std::set; class DLL_LINKAGE ModDescription : boost::noncopyable { + std::unique_ptr localConfig; + std::unique_ptr repositoryConfig; + TModID identifier; TModSet dependencies; TModSet softDependencies; TModSet conflicts; - std::unique_ptr localConfig; - std::unique_ptr repositoryConfig; - TModSet loadModList(const JsonNode & configNode) const; public: - ModDescription(const TModID & fullID, const JsonNode & localConfig); ModDescription(const TModID & fullID, const JsonNode & localConfig, const JsonNode & repositoryConfig); ~ModDescription(); diff --git a/lib/modding/ModManager.cpp b/lib/modding/ModManager.cpp index 3bf41caeb..ce2e6bcb8 100644 --- a/lib/modding/ModManager.cpp +++ b/lib/modding/ModManager.cpp @@ -212,11 +212,11 @@ std::vector ModsPresetState::getActiveMods() const return allActiveMods; } -ModsStorage::ModsStorage(const std::vector & modsToLoad, const std::vector & repositoryList) +ModsStorage::ModsStorage(const std::vector & modsToLoad, const JsonNode & repositoryList) { JsonNode coreModConfig(JsonPath::builtin("config/gameConfig.json")); coreModConfig.setModScope(ModScope::scopeBuiltin()); - mods.try_emplace(ModScope::scopeBuiltin(), ModScope::scopeBuiltin(), coreModConfig); + mods.try_emplace(ModScope::scopeBuiltin(), ModScope::scopeBuiltin(), coreModConfig, JsonNode()); for(auto modID : modsToLoad) { @@ -235,7 +235,18 @@ ModsStorage::ModsStorage(const std::vector & modsToLoad, const std::vect continue; } - mods.try_emplace(modID, modID, modConfig); + mods.try_emplace(modID, modID, modConfig, repositoryList[modID]); + } + + for(const auto & mod : repositoryList.Struct()) + { + if (vstd::contains(modsToLoad, mod.first)) + continue; + + if (mod.second["modType"].isNull() || mod.second["name"].isNull()) + continue; + + mods.try_emplace(mod.first, mod.first, JsonNode(), mod.second); } } @@ -244,12 +255,21 @@ const ModDescription & ModsStorage::getMod(const TModID & fullID) const return mods.at(fullID); } +TModList ModsStorage::getAllMods() const +{ + TModList result; + for (const auto & mod : mods) + result.push_back(mod.first); + + return result; +} + ModManager::ModManager() - :ModManager(std::vector()) + :ModManager(JsonNode()) { } -ModManager::ModManager(const std::vector & repositoryList) +ModManager::ModManager(const JsonNode & repositoryList) : modsState(std::make_unique()) , modsPreset(std::make_unique()) { @@ -267,6 +287,7 @@ ModManager::~ModManager() = default; const ModDescription & ModManager::getModDescription(const TModID & modID) const { + assert(boost::to_lower_copy(modID) == modID); return modsStorage->getMod(modID); } @@ -280,9 +301,9 @@ const TModList & ModManager::getActiveMods() const return activeMods; } -const TModList & ModManager::getAllMods() const +TModList ModManager::getAllMods() const { - return activeMods; //TODO + return modsStorage->getAllMods(); } void ModManager::eraseMissingModsFromPreset() diff --git a/lib/modding/ModManager.h b/lib/modding/ModManager.h index 79804a48b..4d38550e5 100644 --- a/lib/modding/ModManager.h +++ b/lib/modding/ModManager.h @@ -69,9 +69,11 @@ class ModsStorage : boost::noncopyable std::map mods; public: - ModsStorage(const TModList & modsToLoad, const std::vector & repositoryList); + ModsStorage(const TModList & modsToLoad, const JsonNode & repositoryList); const ModDescription & getMod(const TModID & fullID) const; + + TModList getAllMods() const; }; /// Provides public interface to access mod state @@ -92,13 +94,13 @@ class DLL_LINKAGE ModManager : boost::noncopyable void addNewModsToPreset(); public: - ModManager(const std::vector & repositoryList); + ModManager(const JsonNode & repositoryList); ModManager(); ~ModManager(); const ModDescription & getModDescription(const TModID & modID) const; const TModList & getActiveMods() const; - const TModList & getAllMods() const; + TModList getAllMods() const; bool isModActive(const TModID & modID) const; }; diff --git a/vcmiqt/jsonutils.cpp b/vcmiqt/jsonutils.cpp index e4eb884d6..99112bea8 100644 --- a/vcmiqt/jsonutils.cpp +++ b/vcmiqt/jsonutils.cpp @@ -79,7 +79,7 @@ QVariant toVariant(const JsonNode & node) return QVariant(); } -QVariant JsonFromFile(QString filename) +JsonNode jsonFromFile(QString filename) { QFile file(filename); if(!file.open(QFile::ReadOnly)) @@ -90,7 +90,7 @@ QVariant JsonFromFile(QString filename) const auto data = file.readAll(); JsonNode node(reinterpret_cast(data.data()), data.size(), filename.toStdString()); - return toVariant(node); + return node; } JsonNode toJson(QVariant object) @@ -113,10 +113,10 @@ JsonNode toJson(QVariant object) return ret; } -void JsonToFile(QString filename, QVariant object) +void jsonToFile(QString filename, const JsonNode & object) { std::fstream file(qstringToPath(filename).c_str(), std::ios::out | std::ios_base::binary); - file << toJson(object).toString(); + file << object.toString(); } } diff --git a/vcmiqt/jsonutils.h b/vcmiqt/jsonutils.h index 1b78881d6..f65d666b4 100644 --- a/vcmiqt/jsonutils.h +++ b/vcmiqt/jsonutils.h @@ -20,10 +20,10 @@ class JsonNode; namespace JsonUtils { VCMIQT_LINKAGE QVariant toVariant(const JsonNode & node); -VCMIQT_LINKAGE QVariant JsonFromFile(QString filename); +VCMIQT_LINKAGE JsonNode jsonFromFile(QString filename); VCMIQT_LINKAGE JsonNode toJson(QVariant object); -VCMIQT_LINKAGE void JsonToFile(QString filename, QVariant object); +VCMIQT_LINKAGE void jsonToFile(QString filename, const JsonNode & object); } VCMI_LIB_NAMESPACE_END