Handle plugin metadata errors better

This commit is contained in:
Mm2PL 2023-02-14 11:44:52 +01:00
parent d1bcede492
commit cf9d0af105
3 changed files with 119 additions and 58 deletions

View file

@ -30,27 +30,42 @@ struct PluginMeta {
QString homepage; QString homepage;
std::vector<QString> tags; std::vector<QString> tags;
bool valid{}; std::vector<QString> errors;
std::vector<QString> invalidWhy;
bool isValid() const
{
return this->errors.empty();
}
explicit PluginMeta(const QJsonObject &obj) explicit PluginMeta(const QJsonObject &obj)
: homepage(obj.value("homepage").toString("")) : homepage(obj.value("homepage").toString(""))
{ {
auto nameObj = obj.value("name"); auto nameObj = obj.value("name");
if (!nameObj.isString()) if (nameObj.isString())
{ {
this->invalidWhy.emplace_back("name is not a string"); this->name = nameObj.toString();
this->valid = false; }
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"); auto descrObj = obj.value("description");
if (!descrObj.isString()) if (descrObj.isString())
{ {
this->invalidWhy.emplace_back("description is not a string"); this->description = descrObj.toString();
this->valid = false; }
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"); auto authorsObj = obj.value("authors");
if (authorsObj.isArray()) if (authorsObj.isArray())
@ -61,42 +76,60 @@ struct PluginMeta {
const auto &t = authorsArr.at(i); const auto &t = authorsArr.at(i);
if (!t.isString()) if (!t.isString())
{ {
this->invalidWhy.push_back( this->errors.push_back(
QString( QString(
"authors element #%1 is not a string (it is a %2)") "authors element #%1 is not a string (it is a %2)")
.arg(i) .arg(i)
.arg(QString::fromStdString( .arg(QString::fromStdString(
std::string(magic_enum::enum_name(t.type()))))); std::string(magic_enum::enum_name(t.type())))));
this->valid = false; break;
return;
} }
this->authors.push_back(t.toString()); this->authors.push_back(t.toString());
} }
} }
else else
{ {
this->invalidWhy.emplace_back("authors is not an array"); auto type = QString::fromStdString(
this->valid = false; 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"); auto licenseObj = obj.value("license");
if (!licenseObj.isString()) if (licenseObj.isString())
{ {
this->invalidWhy.emplace_back("license is not a string"); this->license = licenseObj.toString();
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();
} }
else else
{ {
this->invalidWhy.emplace_back("unable to parse version"); auto type = QString::fromStdString(
this->valid = false; 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); this->version = semver::version(0, 0, 0);
} }
auto tagsObj = obj.value("tags"); auto tagsObj = obj.value("tags");
@ -104,8 +137,10 @@ struct PluginMeta {
{ {
if (!tagsObj.isArray()) if (!tagsObj.isArray())
{ {
this->invalidWhy.emplace_back("tags is not an array"); auto type = QString::fromStdString(
this->valid = false; 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; return;
} }
@ -115,12 +150,12 @@ struct PluginMeta {
const auto &t = tagsArr.at(i); const auto &t = tagsArr.at(i);
if (!t.isString()) if (!t.isString())
{ {
this->invalidWhy.push_back( this->errors.push_back(
QString("tags element #%1 is not a string (it is a %2)") QString(
"tags element #%1 is not a string (its type is %2)")
.arg(i) .arg(i)
.arg(QString::fromStdString( .arg(QString::fromStdString(
std::string(magic_enum::enum_name(t.type()))))); std::string(magic_enum::enum_name(t.type())))));
this->valid = false;
return; return;
} }
this->tags.push_back(t.toString()); this->tags.push_back(t.toString());

View file

@ -92,14 +92,17 @@ bool PluginController::tryLoadFromDir(const QDir &pluginDir)
} }
auto meta = PluginMeta(doc.object()); auto meta = PluginMeta(doc.object());
if (!meta.invalidWhy.empty()) if (!meta.isValid())
{ {
qCDebug(chatterinoLua) qCDebug(chatterinoLua)
<< "Plugin from" << pluginDir << "is invalid because:"; << "Plugin from" << pluginDir << "is invalid because:";
for (const auto &why : meta.invalidWhy) for (const auto &why : meta.errors)
{ {
qCDebug(chatterinoLua) << "- " << why; qCDebug(chatterinoLua) << "- " << why;
} }
auto plugin = std::make_unique<Plugin>(pluginDir.dirName(), nullptr,
meta, pluginDir);
this->plugins_.insert({pluginDir.dirName(), std::move(plugin)});
return false; return false;
} }
this->load(index, pluginDir, meta); this->load(index, pluginDir, meta);

View file

@ -87,6 +87,25 @@ void PluginsPage::rebuildContent()
} }
auto plgroup = layout.emplace<QGroupBox>(headerText); auto plgroup = layout.emplace<QGroupBox>(headerText);
auto pl = plgroup.setLayoutType<QFormLayout>(); auto pl = plgroup.setLayoutType<QFormLayout>();
if (!plugin->meta.isValid())
{
QString errors = "<ul>";
for (const auto &err : plugin->meta.errors)
{
errors += "<li>" + err.toHtmlEscaped() + "</li>";
}
errors += "</ul>";
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); auto *descrText = new QLabel(plugin->meta.description);
descrText->setWordWrap(true); descrText->setWordWrap(true);
descrText->setStyleSheet("color: #bbb"); descrText->setStyleSheet("color: #bbb");
@ -122,31 +141,35 @@ void PluginsPage::rebuildContent()
} }
pl->addRow("Commands", new QLabel(cmds)); pl->addRow("Commands", new QLabel(cmds));
QString enableOrDisableStr = "Enable"; if (plugin->meta.isValid())
if (PluginController::isEnabled(codename))
{ {
enableOrDisableStr = "Disable"; QString enableOrDisableStr = "Enable";
} if (PluginController::isEnabled(codename))
{
enableOrDisableStr = "Disable";
}
auto *enableDisable = new QPushButton(enableOrDisableStr); auto *enableDisable = new QPushButton(enableOrDisableStr);
QObject::connect( QObject::connect(
enableDisable, &QPushButton::pressed, [name = codename, this]() { enableDisable, &QPushButton::pressed,
std::vector<QString> val = [name = codename, this]() {
getSettings()->enabledPlugins.getValue(); std::vector<QString> val =
if (PluginController::isEnabled(name)) getSettings()->enabledPlugins.getValue();
{ if (PluginController::isEnabled(name))
val.erase(std::remove(val.begin(), val.end(), name), {
val.end()); val.erase(std::remove(val.begin(), val.end(), name),
} val.end());
else }
{ else
val.push_back(name); {
} val.push_back(name);
getSettings()->enabledPlugins.setValue(val); }
getApp()->plugins->reload(name); getSettings()->enabledPlugins.setValue(val);
this->rebuildContent(); getApp()->plugins->reload(name);
}); this->rebuildContent();
pl->addRow(enableDisable); });
pl->addRow(enableDisable);
}
auto *reload = new QPushButton("Reload"); auto *reload = new QPushButton("Reload");
QObject::connect(reload, &QPushButton::pressed, QObject::connect(reload, &QPushButton::pressed,