diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp index 5aef4e585..238032532 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp @@ -57,9 +57,11 @@ GetModDependenciesTask::GetModDependenciesTask(QObject* parent, , m_version(mcVersion(instance)) , m_loaderType(mcLoaders(instance)) { - for (auto mod : folder->allMods()) + for (auto mod : folder->allMods()) { + m_mods_file_names << mod->fileinfo().fileName(); if (auto meta = mod->metadata(); meta) m_mods.append(meta); + } prepare(); } @@ -231,8 +233,13 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen if (dep_.addonId != pDep->version.addonId) { removePack(pDep->version.addonId); addTask(prepareDependencyTask(dep_, provider.name, level)); - } else + } else { addTask(getProjectInfoTask(pDep)); + } + } + if (isLocalyInstalled(pDep)) { + removePack(pDep->version.addonId); + return; } for (auto dep_ : getDependenciesForVersion(pDep->version, provider.name)) { addTask(prepareDependencyTask(dep_, provider.name, level - 1)); @@ -258,9 +265,9 @@ void GetModDependenciesTask::removePack(const QVariant& addonId) #endif } -QHash GetModDependenciesTask::getRequiredBy() +auto GetModDependenciesTask::getExtraInfo() -> QHash { - QHash rby; + QHash rby; auto fullList = m_selected + m_pack_dependencies; for (auto& mod : fullList) { auto addonId = mod->pack->addonId; @@ -282,7 +289,61 @@ QHash GetModDependenciesTask::getRequiredBy() req.append(smod->pack->name); } } - rby[addonId.toString()] = req; + rby[addonId.toString()] = { maybeInstalled(mod), req }; } return rby; } + +// super lax compare (but not fuzzy) +// convert to lowercase +// convert all speratores to whitespace +// simplify sequence of internal whitespace to a single space +// efectivly compare two strings ignoring all separators and case +auto laxCompare = [](QString fsfilename, QString metadataFilename, bool excludeDigits = false) { + // allowed character seperators + QList allowedSeperators = { '-', '+', '.', '_' }; + if (excludeDigits) + allowedSeperators.append({ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }); + + // copy in lowercase + auto fsName = fsfilename.toLower(); + auto metaName = metadataFilename.toLower(); + + // replace all potential allowed seperatores with whitespace + for (auto sep : allowedSeperators) { + fsName = fsName.replace(sep, ' '); + metaName = metaName.replace(sep, ' '); + } + + // remove extraneous whitespace + fsName = fsName.simplified(); + metaName = metaName.simplified(); + + return fsName.compare(metaName) == 0; +}; + +bool GetModDependenciesTask::isLocalyInstalled(std::shared_ptr pDep) +{ + return pDep->version.fileName.isEmpty() || + + std::find_if(m_selected.begin(), m_selected.end(), + [pDep](std::shared_ptr i) { + return !i->version.fileName.isEmpty() && laxCompare(i->version.fileName, pDep->version.fileName); + }) != m_selected.end() || // check the selected versions + + std::find_if(m_mods_file_names.begin(), m_mods_file_names.end(), + [pDep](QString i) { return !i.isEmpty() && laxCompare(i, pDep->version.fileName); }) != + m_mods_file_names.end() || // check the existing mods + + std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(), [pDep](std::shared_ptr i) { + return pDep->pack->addonId != i->pack->addonId && !i->version.fileName.isEmpty() && + laxCompare(pDep->version.fileName, i->version.fileName); + }) != m_pack_dependencies.end(); // check loaded dependencies +} + +bool GetModDependenciesTask::maybeInstalled(std::shared_ptr pDep) +{ + return std::find_if(m_mods_file_names.begin(), m_mods_file_names.end(), [pDep](QString i) { + return !i.isEmpty() && laxCompare(i, pDep->version.fileName, true); + }) != m_mods_file_names.end(); // check the existing mods +} diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h index e88204bdc..7202b01e0 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h @@ -50,6 +50,11 @@ class GetModDependenciesTask : public SequentialTask { } }; + struct PackDependencyExtraInfo { + bool maybe_installed; + QStringList required_by; + }; + struct Provider { ModPlatform::ResourceProvider name; std::shared_ptr mod; @@ -62,7 +67,7 @@ class GetModDependenciesTask : public SequentialTask { QList> selected); auto getDependecies() const -> QList> { return m_pack_dependencies; } - QHash getRequiredBy(); + QHash getExtraInfo(); protected slots: Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, ModPlatform::ResourceProvider, int); @@ -73,10 +78,14 @@ class GetModDependenciesTask : public SequentialTask { ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, ModPlatform::ResourceProvider providerName); void removePack(const QVariant& addonId); + bool isLocalyInstalled(std::shared_ptr pDep); + bool maybeInstalled(std::shared_ptr pDep); + private: QList> m_pack_dependencies; QList> m_mods; QList> m_selected; + QStringList m_mods_file_names; Provider m_flame_provider; Provider m_modrinth_provider; diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index 8bd83d988..b19b25484 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -28,6 +28,7 @@ class CheckUpdateTask : public Task { QString changelog; ModPlatform::ResourceProvider provider; shared_qobject_ptr download; + bool enabled = true; public: UpdatableMod(QString name, @@ -37,7 +38,8 @@ class CheckUpdateTask : public Task { std::optional new_v_type, QString changelog, ModPlatform::ResourceProvider p, - shared_qobject_ptr t) + shared_qobject_ptr t, + bool enabled = true) : name(name) , old_hash(old_h) , old_version(old_v) @@ -46,6 +48,7 @@ class CheckUpdateTask : public Task { , changelog(changelog) , provider(p) , download(t) + , enabled(enabled) {} }; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 190638487..54893d775 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -214,19 +214,25 @@ void ModUpdateDialog::checkCandidates() } static FlameAPI api; - auto getRequiredBy = depTask->getRequiredBy(); + auto dependencyExtraInfo = depTask->getExtraInfo(); for (auto dep : depTask->getDependecies()) { auto changelog = dep->version.changelog; if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); auto download_task = makeShared(dep->pack, dep->version, m_mod_model); - CheckUpdateTask::UpdatableMod updatable = { - dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type, - changelog, dep->pack->provider, download_task - }; + auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString()); + CheckUpdateTask::UpdatableMod updatable = { dep->pack->name, + dep->version.hash, + "", + dep->version.version, + dep->version.version_type, + changelog, + dep->pack->provider, + download_task, + !extraInfo.maybe_installed }; - appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString())); + appendMod(updatable, extraInfo.required_by); m_tasks.insert(updatable.name, updatable.download); } } @@ -412,7 +418,10 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy) { auto item_top = new QTreeWidgetItem(ui->modTreeWidget); - item_top->setCheckState(0, Qt::CheckState::Checked); + item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + if (!info.enabled) { + item_top->setToolTip(0, tr("Mod was disabled as it may be already instaled.")); + } item_top->setText(0, info.name); item_top->setExpanded(true); diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 1431ea92c..6d28cea1f 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -132,7 +132,7 @@ void ResourceDownloadDialog::confirm() auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); confirm_dialog->retranslateUi(resourcesString()); - QHash getRequiredBy; + QHash dependencyExtraInfo; if (auto task = getModDependenciesTask(); task) { connect(task.get(), &Task::failed, this, [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); @@ -157,7 +157,7 @@ void ResourceDownloadDialog::confirm() } else { for (auto dep : task->getDependecies()) addResource(dep->pack, dep->version); - getRequiredBy = task->getRequiredBy(); + dependencyExtraInfo = task->getExtraInfo(); } } @@ -166,9 +166,10 @@ void ResourceDownloadDialog::confirm() return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0; }); for (auto& task : selected) { + auto extraInfo = dependencyExtraInfo.value(task->getPack()->addonId.toString()); confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(), - ProviderCaps.name(task->getProvider()), getRequiredBy.value(task->getPack()->addonId.toString()), - task->getVersion().version_type.toString() }); + ProviderCaps.name(task->getProvider()), extraInfo.required_by, + task->getVersion().version_type.toString(), !extraInfo.maybe_installed }); } if (confirm_dialog->exec()) { diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index 41b832e03..66c36d400 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -35,8 +35,11 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon) void ReviewMessageBox::appendResource(ResourceInformation&& info) { auto itemTop = new QTreeWidgetItem(ui->modTreeWidget); - itemTop->setCheckState(0, Qt::CheckState::Checked); + itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); itemTop->setText(0, info.name); + if (!info.enabled) { + itemTop->setToolTip(0, tr("Mod was disabled as it may be already instaled.")); + } auto filenameItem = new QTreeWidgetItem(itemTop); filenameItem->setText(0, tr("Filename: %1").arg(info.filename)); diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h index 7dd2732a0..82a43bc11 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.h +++ b/launcher/ui/dialogs/ReviewMessageBox.h @@ -20,6 +20,7 @@ class ReviewMessageBox : public QDialog { QString provider; QStringList required_by; QString version_type; + bool enabled = true; }; void appendResource(ResourceInformation&& info);