From cf9d0af10590b624dbc07609c3fa360d765aa791 Mon Sep 17 00:00:00 2001 From: Mm2PL Date: Tue, 14 Feb 2023 11:44:52 +0100 Subject: [PATCH] Handle plugin metadata errors better --- src/controllers/plugins/Plugin.hpp | 101 +++++++++++++------ src/controllers/plugins/PluginController.cpp | 7 +- src/widgets/settingspages/PluginsPage.cpp | 69 ++++++++----- 3 files changed, 119 insertions(+), 58 deletions(-) diff --git a/src/controllers/plugins/Plugin.hpp b/src/controllers/plugins/Plugin.hpp index 177a8c617..5f96af744 100644 --- a/src/controllers/plugins/Plugin.hpp +++ b/src/controllers/plugins/Plugin.hpp @@ -30,27 +30,42 @@ struct PluginMeta { QString homepage; std::vector tags; - bool valid{}; - std::vector invalidWhy; + std::vector errors; + + bool isValid() const + { + return this->errors.empty(); + } explicit PluginMeta(const QJsonObject &obj) : homepage(obj.value("homepage").toString("")) { auto nameObj = obj.value("name"); - if (!nameObj.isString()) + if (nameObj.isString()) { - this->invalidWhy.emplace_back("name is not a string"); - this->valid = false; + this->name = nameObj.toString(); + } + else + { + auto type = QString::fromStdString( + std::string(magic_enum::enum_name(nameObj.type()))); + this->errors.emplace_back( + QString("name is not a string (its type is %1)").arg(type)); } - this->name = nameObj.toString(); auto descrObj = obj.value("description"); - if (!descrObj.isString()) + if (descrObj.isString()) { - this->invalidWhy.emplace_back("description is not a string"); - this->valid = false; + this->description = descrObj.toString(); + } + else + { + auto type = QString::fromStdString( + std::string(magic_enum::enum_name(descrObj.type()))); + this->errors.emplace_back( + QString("description is not a string (its type is %1)") + .arg(type)); } - this->description = descrObj.toString(); auto authorsObj = obj.value("authors"); if (authorsObj.isArray()) @@ -61,42 +76,60 @@ struct PluginMeta { const auto &t = authorsArr.at(i); if (!t.isString()) { - this->invalidWhy.push_back( + this->errors.push_back( QString( "authors element #%1 is not a string (it is a %2)") .arg(i) .arg(QString::fromStdString( std::string(magic_enum::enum_name(t.type()))))); - this->valid = false; - return; + break; } this->authors.push_back(t.toString()); } } else { - this->invalidWhy.emplace_back("authors is not an array"); - this->valid = false; + auto type = QString::fromStdString( + std::string(magic_enum::enum_name(authorsObj.type()))); + this->errors.emplace_back( + QString("authors is not an array (its type is %1)").arg(type)); } auto licenseObj = obj.value("license"); - if (!licenseObj.isString()) + if (licenseObj.isString()) { - this->invalidWhy.emplace_back("license is not a string"); - this->valid = false; - } - this->license = licenseObj.toString(); - - auto v = semver::from_string_noexcept( - obj.value("version").toString().toStdString()); - if (v.has_value()) - { - this->version = v.value(); + this->license = licenseObj.toString(); } else { - this->invalidWhy.emplace_back("unable to parse version"); - this->valid = false; + auto type = QString::fromStdString( + std::string(magic_enum::enum_name(licenseObj.type()))); + this->errors.emplace_back( + QString("license is not a string (its type is %1)").arg(type)); + } + + auto verObj = obj.value("version"); + if (verObj.isString()) + { + auto v = + semver::from_string_noexcept(verObj.toString().toStdString()); + if (v.has_value()) + { + this->version = v.value(); + } + else + { + this->errors.emplace_back( + "unable to parse version (use semver)"); + this->version = semver::version(0, 0, 0); + } + } + else + { + auto type = QString::fromStdString( + std::string(magic_enum::enum_name(verObj.type()))); + this->errors.emplace_back( + QString("version is not a string (its type is %1)").arg(type)); this->version = semver::version(0, 0, 0); } auto tagsObj = obj.value("tags"); @@ -104,8 +137,10 @@ struct PluginMeta { { if (!tagsObj.isArray()) { - this->invalidWhy.emplace_back("tags is not an array"); - this->valid = false; + auto type = QString::fromStdString( + std::string(magic_enum::enum_name(licenseObj.type()))); + this->errors.emplace_back( + QString("tags is not an array (its type is %1)").arg(type)); return; } @@ -115,12 +150,12 @@ struct PluginMeta { const auto &t = tagsArr.at(i); if (!t.isString()) { - this->invalidWhy.push_back( - QString("tags element #%1 is not a string (it is a %2)") + this->errors.push_back( + QString( + "tags element #%1 is not a string (its type is %2)") .arg(i) .arg(QString::fromStdString( std::string(magic_enum::enum_name(t.type()))))); - this->valid = false; return; } this->tags.push_back(t.toString()); diff --git a/src/controllers/plugins/PluginController.cpp b/src/controllers/plugins/PluginController.cpp index febe8367a..1b91c3927 100644 --- a/src/controllers/plugins/PluginController.cpp +++ b/src/controllers/plugins/PluginController.cpp @@ -92,14 +92,17 @@ bool PluginController::tryLoadFromDir(const QDir &pluginDir) } auto meta = PluginMeta(doc.object()); - if (!meta.invalidWhy.empty()) + if (!meta.isValid()) { qCDebug(chatterinoLua) << "Plugin from" << pluginDir << "is invalid because:"; - for (const auto &why : meta.invalidWhy) + for (const auto &why : meta.errors) { qCDebug(chatterinoLua) << "- " << why; } + auto plugin = std::make_unique(pluginDir.dirName(), nullptr, + meta, pluginDir); + this->plugins_.insert({pluginDir.dirName(), std::move(plugin)}); return false; } this->load(index, pluginDir, meta); diff --git a/src/widgets/settingspages/PluginsPage.cpp b/src/widgets/settingspages/PluginsPage.cpp index 596280519..591e67419 100644 --- a/src/widgets/settingspages/PluginsPage.cpp +++ b/src/widgets/settingspages/PluginsPage.cpp @@ -87,6 +87,25 @@ void PluginsPage::rebuildContent() } auto plgroup = layout.emplace(headerText); auto pl = plgroup.setLayoutType(); + + if (!plugin->meta.isValid()) + { + QString errors = "
    "; + for (const auto &err : plugin->meta.errors) + { + errors += "
  • " + err.toHtmlEscaped() + "
  • "; + } + errors += "
"; + + auto *warningLabel = new QLabel( + "There were errors while loading metadata for this plugin:" + + errors); + warningLabel->setTextFormat(Qt::RichText); + warningLabel->setParent(this->dataFrame_); + warningLabel->setStyleSheet("color: #f00"); + pl->addRow(warningLabel); + } + auto *descrText = new QLabel(plugin->meta.description); descrText->setWordWrap(true); descrText->setStyleSheet("color: #bbb"); @@ -122,31 +141,35 @@ void PluginsPage::rebuildContent() } pl->addRow("Commands", new QLabel(cmds)); - QString enableOrDisableStr = "Enable"; - if (PluginController::isEnabled(codename)) + if (plugin->meta.isValid()) { - enableOrDisableStr = "Disable"; - } + QString enableOrDisableStr = "Enable"; + if (PluginController::isEnabled(codename)) + { + enableOrDisableStr = "Disable"; + } - auto *enableDisable = new QPushButton(enableOrDisableStr); - QObject::connect( - enableDisable, &QPushButton::pressed, [name = codename, this]() { - std::vector val = - getSettings()->enabledPlugins.getValue(); - if (PluginController::isEnabled(name)) - { - val.erase(std::remove(val.begin(), val.end(), name), - val.end()); - } - else - { - val.push_back(name); - } - getSettings()->enabledPlugins.setValue(val); - getApp()->plugins->reload(name); - this->rebuildContent(); - }); - pl->addRow(enableDisable); + auto *enableDisable = new QPushButton(enableOrDisableStr); + QObject::connect( + enableDisable, &QPushButton::pressed, + [name = codename, this]() { + std::vector val = + getSettings()->enabledPlugins.getValue(); + if (PluginController::isEnabled(name)) + { + val.erase(std::remove(val.begin(), val.end(), name), + val.end()); + } + else + { + val.push_back(name); + } + getSettings()->enabledPlugins.setValue(val); + getApp()->plugins->reload(name); + this->rebuildContent(); + }); + pl->addRow(enableDisable); + } auto *reload = new QPushButton("Reload"); QObject::connect(reload, &QPushButton::pressed,