diff options
author | Marcus Tillmanns <[email protected]> | 2025-03-06 11:09:00 +0100 |
---|---|---|
committer | Marcus Tillmanns <[email protected]> | 2025-03-06 12:04:29 +0000 |
commit | 7df8102cec707c7081bcbe797015de082990f921 (patch) | |
tree | b0fa96bccabcc82c55beccef67e654aea82e1ba6 /src/plugins/extensionmanager | |
parent | 2793470a2c111c186abc4a1d12b77077fb1b0e04 (diff) |
ExtensionManager: Add RemoteSpec to simplify data access
RemoteSpec stores all the data about a remote plugin or pack.
It uses the PluginSpec::readMetaData mechanism to parse
the metadata information.
Change-Id: I1d0c90615f0c0e54876cbfeb5d0a2a72be8796e8
Reviewed-by: Alessandro Portale <[email protected]>
Diffstat (limited to 'src/plugins/extensionmanager')
-rw-r--r-- | src/plugins/extensionmanager/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/plugins/extensionmanager/extensionmanager.qbs | 2 | ||||
-rw-r--r-- | src/plugins/extensionmanager/extensionmanagerwidget.cpp | 13 | ||||
-rw-r--r-- | src/plugins/extensionmanager/extensionsmodel.cpp | 231 | ||||
-rw-r--r-- | src/plugins/extensionmanager/extensionsmodel.h | 1 | ||||
-rw-r--r-- | src/plugins/extensionmanager/remotespec.cpp | 214 | ||||
-rw-r--r-- | src/plugins/extensionmanager/remotespec.h | 84 |
7 files changed, 423 insertions, 124 deletions
diff --git a/src/plugins/extensionmanager/CMakeLists.txt b/src/plugins/extensionmanager/CMakeLists.txt index 56236df9fb7..8340de04149 100644 --- a/src/plugins/extensionmanager/CMakeLists.txt +++ b/src/plugins/extensionmanager/CMakeLists.txt @@ -13,6 +13,8 @@ add_qtc_plugin(ExtensionManager extensionsbrowser.h extensionsmodel.cpp extensionsmodel.h + remotespec.cpp + remotespec.h ) extend_qtc_plugin(ExtensionManager diff --git a/src/plugins/extensionmanager/extensionmanager.qbs b/src/plugins/extensionmanager/extensionmanager.qbs index 994d75f7366..fd41974a8b1 100644 --- a/src/plugins/extensionmanager/extensionmanager.qbs +++ b/src/plugins/extensionmanager/extensionmanager.qbs @@ -21,6 +21,8 @@ QtcPlugin { "extensionsbrowser.h", "extensionsmodel.cpp", "extensionsmodel.h", + "remotespec.cpp", + "remotespec.h", ] QtcTestFiles { diff --git a/src/plugins/extensionmanager/extensionmanagerwidget.cpp b/src/plugins/extensionmanager/extensionmanagerwidget.cpp index bb07cf34d25..ffea1964f28 100644 --- a/src/plugins/extensionmanager/extensionmanagerwidget.cpp +++ b/src/plugins/extensionmanager/extensionmanagerwidget.cpp @@ -7,6 +7,7 @@ #include "extensionmanagertr.h" #include "extensionsbrowser.h" #include "extensionsmodel.h" +#include "remotespec.h" #include <coreplugin/coreconstants.h> #include <coreplugin/coreplugintr.h> @@ -452,6 +453,8 @@ private: QString m_currentDownloadUrl; QString m_currentId; Tasking::TaskTreeRunner m_dlTaskTreeRunner; + + std::unique_ptr<RemoteSpec> m_remoteSpec; }; static QWidget *descriptionPlaceHolder() @@ -525,6 +528,12 @@ ExtensionManagerWidget::ExtensionManagerWidget() m_packExtensionsTitle = sectionTitle(h6TF, Tr::tr("Extensions in pack")); m_packExtensions = new QLabel; applyTf(m_packExtensions, contentTF, false); + + m_packExtensions->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + connect(m_packExtensions, &QLabel::linkActivated, this, [this](const QString &link) { + m_extensionBrowser->selectIndex(m_extensionModel->indexOfId(link)); + }); + m_pluginStatus = new PluginStatusWidget; auto secondary = new QWidget; @@ -637,7 +646,9 @@ void ExtensionManagerWidget::updateView(const QModelIndex ¤t) auto idToDisplayName = [this](const QString &id) { const QModelIndex dependencyIndex = m_extensionModel->indexOfId(id); - const QString displayName = dependencyIndex.data(RoleName).toString(); + QString displayName = dependencyIndex.data(RoleName).toString(); + if (displayName.isEmpty()) + displayName = id; return QString("<a href=\"%1\">%2</a>").arg(id).arg(displayName); }; diff --git a/src/plugins/extensionmanager/extensionsmodel.cpp b/src/plugins/extensionmanager/extensionsmodel.cpp index e31b3960ac2..e7c52ba2b90 100644 --- a/src/plugins/extensionmanager/extensionsmodel.cpp +++ b/src/plugins/extensionmanager/extensionsmodel.cpp @@ -4,6 +4,7 @@ #include "extensionsmodel.h" #include "extensionmanagertr.h" +#include "remotespec.h" #include <utils/algorithm.h> #include <utils/hostosinfo.h> @@ -28,8 +29,6 @@ using namespace Utils; namespace ExtensionManager::Internal { -const char EXTENSION_KEY_ID[] = "id"; - Q_LOGGING_CATEGORY(modelLog, "qtc.extensionmanager.model", QtWarningMsg) class ExtensionsModelPrivate @@ -37,27 +36,24 @@ class ExtensionsModelPrivate public: void addUnlistedLocalPlugins(); - static QVariant dataFromRemotePack(const QJsonObject &json, int role); - static QVariant dataFromRemotePlugin(const QJsonObject &json, int role); + static QVariant dataFromRemotePack(const RemoteSpec *spec, int role); + static QVariant dataFromRemotePlugin(const RemoteSpec *spec, int role); QVariant dataFromRemoteExtension(int index, int role) const; QVariant dataFromLocalPlugin(int index, int role) const; - QJsonArray responseItems; + //QJsonArray responseItems; PluginSpecs localPlugins; + std::vector<std::unique_ptr<RemoteSpec>> remotePlugins; }; void ExtensionsModelPrivate::addUnlistedLocalPlugins() { - QSet<QString> responseExtensions; - for (const QJsonValueConstRef &responseItem : qAsConst(responseItems)) - responseExtensions << responseItem.toObject().value("id").toString(); - - localPlugins.clear(); - for (PluginSpec *plugin : PluginManager::plugins()) - if (!responseExtensions.contains(plugin->id())) - localPlugins.append(plugin); + QSet<QString> remoteIds = Utils::transform<QSet>(remotePlugins, &RemoteSpec::id); + localPlugins = Utils::filtered(PluginManager::plugins(), [&remoteIds](const PluginSpec *plugin) { + return !remoteIds.contains(plugin->id()); + }); - qCDebug(modelLog) << "Number of extensions from JSON:" << responseExtensions.count(); + qCDebug(modelLog) << "Number of extensions from JSON:" << remotePlugins.size(); qCDebug(modelLog) << "Number of added local plugins:" << localPlugins.count(); } @@ -85,128 +81,79 @@ QString descriptionWithLinks(const QString &description, const QString &url, return fragments.join("\n\n"); } -QVariant ExtensionsModelPrivate::dataFromRemotePack(const QJsonObject &json, int role) -{ - switch (role) { - case RoleDescriptionLong: - return joinedStringList(json.value("long_description")); - case RoleDescriptionShort: - return joinedStringList(json.value("description")); - case RoleItemType: - return ItemTypePack; - case RolePlugins: - return json.value("plugins").toVariant().toStringList(); - default: - break; - } - - return {}; -} - -QVariant ExtensionsModelPrivate::dataFromRemotePlugin(const QJsonObject &json, int role) +QVariant ExtensionsModelPrivate::dataFromRemoteExtension(int index, int role) const { - const QJsonObject metaData = json.value("metadata").toObject(); + QTC_ASSERT(index >= 0 && size_t(index) < remotePlugins.size(), return {}); + RemoteSpec *remoteSpec = remotePlugins.at(index).get(); switch (role) { + case Qt::DisplayRole: + case RoleName: + return remoteSpec->displayName(); + case RoleDownloadCount: + return remoteSpec->downloads(); + case RoleId: + return remoteSpec->id(); + case RoleDateUpdated: + return remoteSpec->updatedAt(); + case RoleStatus: + return remoteSpec->statusString(); + case RoleTags: + return remoteSpec->tags(); + case RoleVendor: + return remoteSpec->vendor(); + case RoleVendorId: + return remoteSpec->vendorId(); case RoleCopyright: - return metaData.value("Copyright"); + return remoteSpec->copyright(); case RoleDownloadUrl: { - const QJsonArray sources = json.value("sources").toArray(); - const QString thisPlatform = customOsTypeToString(HostOsInfo::hostOs()); - const QString thisArch = QSysInfo::currentCpuArchitecture(); - for (const QJsonValue &source : sources) { - const QJsonObject sourceObject = source.toObject(); - const QJsonObject platform = sourceObject.value("platform").toObject(); - if (platform.isEmpty() // Might be a Lua plugin - || (platform.value("name").toString() == thisPlatform - && platform.value("architecture") == thisArch)) - return sourceObject.value("url").toString(); + for (const auto &source : remoteSpec->sources()) { + if (!source.platform) + return source.url; + + if (source.platform->os == HostOsInfo::hostOs() + && source.platform->architecture == HostOsInfo::hostArchitecture()) + return source.url; } - break; + return {}; } case RoleDependencies: { - QStringList dependencies; - - const QJsonArray dependenciesArray = metaData.value("Dependencies").toArray(); - for (const auto &dependency : dependenciesArray) { - const QJsonObject dependencyObject = dependency.toObject(); - dependencies.append(dependencyObject.value("Id").toString()); - } - + QStringList dependencies + = Utils::transform(remoteSpec->dependencies(), &PluginDependency::id); return dependencies; } case RolePlatforms: { - QSet<QString> platforms; - const QJsonArray sources = json.value("sources").toArray(); - for (const QJsonValue &source : sources) { - // {"name": "Windows", "architecture": "x86" } - const QJsonObject platform = source.toObject().value("platform").toObject(); - if (!platform.isEmpty()) { - const QString name = customOsTypeToString( - osTypeFromString(platform.value("name").toString()).value_or(OsTypeOther)); - const QString architecture = platform.value("architecture").toString(); - platforms.insert(name + " " + architecture); - } else { - platforms.insert(Tr::tr("Platform agnostic")); - } - } - - return platforms.values(); + QStringList platforms + = Utils::transform<QStringList>(remoteSpec->sources(), [](const Source &s) -> QString { + if (!s.platform) + return Tr::tr("Platform agnostic"); + + const QString name = customOsTypeToString(s.platform->os); + const QString architecture = customOsArchToString(s.platform->architecture); + return name + " " + architecture; + }); + platforms.sort(Qt::CaseInsensitive); + return Utils::filteredUnique(platforms); } case RoleVersion: - return metaData.value("Version"); + return remoteSpec->version(); case RoleItemType: + if (remoteSpec->isPack()) + return ItemTypePack; + return ItemTypeExtension; case RoleDescriptionLong: { - const QString description = joinedStringList(metaData.value("LongDescription")); - const QString url = metaData.value("Url").toString(); - const QString documentationUrl = metaData.value("DocumentationUrl").toString(); + const QString description = remoteSpec->longDescription(); + const QString url = remoteSpec->url(); + const QString documentationUrl = remoteSpec->documentationUrl(); return descriptionWithLinks(description, url, documentationUrl); } case RoleDescriptionShort: - return joinedStringList(metaData.value("Description")); - default: - break; - } - - return {}; -} - -QVariant ExtensionsModelPrivate::dataFromRemoteExtension(int index, int role) const -{ - const QJsonObject json = responseItems.at(index).toObject(); - - switch (role) { - case Qt::DisplayRole: - case RoleName: - return json.value("display_name"); - case RoleDownloadCount: - break; // TODO: Reinstate download numbers when they have more substance - // return json.value("downloads"); - case RoleId: - return json.value(EXTENSION_KEY_ID); - case RoleDateUpdated: - return QDate::fromString(json.value("updated_at").toString(), Qt::ISODate); - case RoleStatus: - return json.value("status"); - case RoleTags: - return json.value("tags").toVariant().toStringList(); - case RoleVendor: - return json.value("display_vendor"); - case RoleVendorId: - return json.value("vendor_id"); - default: - break; + return remoteSpec->description(); + case RolePlugins: + return remoteSpec->packPluginIds(); } - const QJsonObject pluginObject = json.value("plugin").toObject(); - if (!pluginObject.isEmpty()) - return dataFromRemotePlugin(pluginObject, role); - - const QJsonObject packObject = json.value("pack").toObject(); - if (!packObject.isEmpty()) - return dataFromRemotePack(packObject, role); - return {}; } @@ -270,7 +217,7 @@ ExtensionsModel::~ExtensionsModel() int ExtensionsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const { - return d->responseItems.count() + d->localPlugins.count(); + return d->remotePlugins.size() + d->localPlugins.count(); } static QString badgeText(const QModelIndex &index) @@ -324,8 +271,8 @@ QVariant ExtensionsModel::data(const QModelIndex &index, int role) const break; } - const bool isRemoteExtension = index.row() < d->responseItems.count(); - const int itemIndex = index.row() - (isRemoteExtension ? 0 : d->responseItems.count()); + const bool isRemoteExtension = size_t(index.row()) < d->remotePlugins.size(); + const int itemIndex = index.row() - (isRemoteExtension ? 0 : d->remotePlugins.size()); return isRemoteExtension ? d->dataFromRemoteExtension(itemIndex, role) : d->dataFromLocalPlugin(itemIndex, role); @@ -335,10 +282,11 @@ QModelIndex ExtensionsModel::indexOfId(const QString &extensionId) const { const int localIndex = indexOf(d->localPlugins, equal(&PluginSpec::id, extensionId)); if (localIndex >= 0) - return index(d->responseItems.count() + localIndex); + return index(d->remotePlugins.size() + localIndex); - for (int remoteIndex = 0; const QJsonValueConstRef &value : std::as_const(d->responseItems)) { - if (value.toObject().value(EXTENSION_KEY_ID) == extensionId) + for (int remoteIndex = 0; + const std::unique_ptr<RemoteSpec> &spec : std::as_const(d->remotePlugins)) { + if (spec->id() == extensionId) return index(remoteIndex); ++remoteIndex; } @@ -351,8 +299,22 @@ void ExtensionsModel::setExtensionsJson(const QByteArray &json) beginResetModel(); QJsonParseError error; const QJsonObject jsonObj = QJsonDocument::fromJson(json, &error).object(); + d->remotePlugins.clear(); + qCDebug(modelLog) << "QJsonParseError:" << error.errorString(); - d->responseItems = jsonObj.value("items").toArray(); + QJsonArray responseItems = jsonObj.value("items").toArray(); + + for (const QJsonValue &item : responseItems) { + const QJsonObject itemObject = item.toObject(); + std::unique_ptr<RemoteSpec> remoteSpec(new RemoteSpec()); + auto result = remoteSpec->fromJson(itemObject); + if (!result) { + qCWarning(modelLog) << "Failed to read remote extension:" << result.error(); + continue; + } + d->remotePlugins.push_back(std::move(remoteSpec)); + } + d->addUnlistedLocalPlugins(); endResetModel(); } @@ -374,9 +336,32 @@ QString customOsTypeToString(OsType osType) } } +QString customOsArchToString(OsArch osArch) +{ + switch (osArch) { + case OsArchX86: + return "x86"; + case OsArchAMD64: + return "x86_64"; + case OsArchItanium: + return "ia64"; + case OsArchArm: + return "arm"; + case OsArchArm64: + return "arm64"; + case OsArchUnknown: + break; + } + + return "Unknown"; +} + PluginSpec *pluginSpecForId(const QString &pluginId) { - return findOrDefault(PluginManager::plugins(), equal(&PluginSpec::id, pluginId)); + PluginSpec *spec = findOrDefault(PluginManager::plugins(), equal(&PluginSpec::id, pluginId)); + if (spec) + return spec; + return nullptr; } QString statusDisplayString(const QModelIndex &index) diff --git a/src/plugins/extensionmanager/extensionsmodel.h b/src/plugins/extensionmanager/extensionsmodel.h index 87b4a6d73b6..bd973852398 100644 --- a/src/plugins/extensionmanager/extensionsmodel.h +++ b/src/plugins/extensionmanager/extensionsmodel.h @@ -66,6 +66,7 @@ private: }; QString customOsTypeToString(Utils::OsType osType); +QString customOsArchToString(Utils::OsArch osArch); ExtensionSystem::PluginSpec *pluginSpecForId(const QString &pluginId); QString statusDisplayString(const QModelIndex &index); diff --git a/src/plugins/extensionmanager/remotespec.cpp b/src/plugins/extensionmanager/remotespec.cpp new file mode 100644 index 00000000000..e546ce7831a --- /dev/null +++ b/src/plugins/extensionmanager/remotespec.cpp @@ -0,0 +1,214 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "remotespec.h" + +#include <utils/stringutils.h> + +#include <QJsonArray> +#include <QJsonDocument> + +using namespace Utils; + +namespace ExtensionManager::Internal { +bool RemoteSpec::loadLibrary() +{ + return false; +}; +bool RemoteSpec::initializePlugin() +{ + return false; +}; +bool RemoteSpec::initializeExtensions() +{ + return false; +}; +bool RemoteSpec::delayedInitialize() +{ + return false; +}; +ExtensionSystem::IPlugin::ShutdownFlag RemoteSpec::stop() +{ + return ExtensionSystem::IPlugin::SynchronousShutdown; +}; + +void RemoteSpec::kill() {}; + +ExtensionSystem::IPlugin *RemoteSpec::plugin() const +{ + return nullptr; +}; + +FilePath RemoteSpec::installLocation(bool inUserFolder) const +{ + Q_UNUSED(inUserFolder); + return {}; +}; + +Result RemoteSpec::fromJson(const QJsonObject &remoteJsonData) +{ + m_remoteJsonData = remoteJsonData; + + const QJsonObject plugin = pluginObject(); + + if (!plugin.isEmpty()) { + auto res = ExtensionSystem::PluginSpec::readMetaData(plugin.value("metadata").toObject()); + if (!res) + return Result::Error(res.error()); + if (hasError()) + return Result::Error(errorString()); + return Result::Ok; + } + + m_isPack = true; + + return Result::Ok; +} + +const QList<Source> RemoteSpec::sources() const +{ + const auto obj = pluginObject(); + if (obj.isEmpty()) + return {}; + + QList<Source> sources; + for (const QJsonValue &source : obj.value("sources").toArray()) { + Source s; + const QJsonObject platform = source.toObject().value("platform").toObject(); + if (!platform.isEmpty()) { + s.platform = Source::Platform{ + osTypeFromString(platform.value("name").toString()).value_or(OsTypeOther), + osArchFromString(platform.value("architecture").toString()).value_or(OsArchUnknown)}; + } + s.url = source.toObject().value("url").toString(); + sources.append(s); + } + return sources; +} + +QList<QString> RemoteSpec::tags() const +{ + return m_remoteJsonData.value("tags").toVariant().toStringList(); +} +QDateTime RemoteSpec::createdAt() const +{ + return QDateTime::fromString(m_remoteJsonData.value("created_at").toString(), Qt::ISODate); +} +QDateTime RemoteSpec::updatedAt() const +{ + return QDateTime::fromString(m_remoteJsonData.value("updated_at").toString(), Qt::ISODate); +} +QDateTime RemoteSpec::releasedAt() const +{ + return QDateTime::fromString(m_remoteJsonData.value("released_at").toString(), Qt::ISODate); +} +int RemoteSpec::downloads() const +{ + return m_remoteJsonData.value("downloads").toInt(); +} +QString RemoteSpec::icon() const +{ + return m_remoteJsonData.value("icon").toString(); +} +QString RemoteSpec::smallIcon() const +{ + return m_remoteJsonData.value("small_icon").toString(); +} +bool RemoteSpec::isLatest() const +{ + return m_remoteJsonData.value("is_latest").toBool(); +} +Status RemoteSpec::status() const +{ + const QString status = m_remoteJsonData.value("status").toString(); + if (status == "published") { + return Status::Published; + } else if (status == "unpublished") { + return Status::Unpublished; + } + return Status::Published; +} + +QString RemoteSpec::uid() const +{ + return m_remoteJsonData.value("uid").toString(); +} + +QString RemoteSpec::statusString() const +{ + return m_remoteJsonData.value("status").toString(); +} +QJsonObject RemoteSpec::packObject() const +{ + return m_remoteJsonData.value("pack").toObject(); +} +QJsonObject RemoteSpec::pluginObject() const +{ + return m_remoteJsonData.value("plugin").toObject(); +} +bool RemoteSpec::isPack() const +{ + return m_isPack; +} +QString RemoteSpec::description() const +{ + if (isPack()) { + QString result; + if (readMultiLineString(packObject().value("description"), &result)) + return result; + return {}; + } + + return PluginSpec::description(); +} + +QString RemoteSpec::longDescription() const +{ + if (isPack()) { + QString result; + if (readMultiLineString(packObject().value("long_description"), &result)) + return result; + return {}; + } + + return PluginSpec::longDescription(); +} + +QStringList RemoteSpec::packPluginIds() const +{ + return packObject().value("plugins").toVariant().toStringList(); +} + +QString RemoteSpec::id() const +{ + if (isPack()) + return m_remoteJsonData.value("id").toString(); + + return PluginSpec::id(); +} + +QString RemoteSpec::displayName() const +{ + if (isPack()) + return m_remoteJsonData.value("display_name").toString(); + + return PluginSpec::displayName(); +} + +QString RemoteSpec::vendor() const +{ + if (isPack()) + return m_remoteJsonData.value("vendor").toString(); + + return PluginSpec::vendor(); +} + +QString RemoteSpec::vendorId() const +{ + if (isPack()) + return m_remoteJsonData.value("vendor_id").toString(); + + return PluginSpec::vendorId(); +} + +} // namespace ExtensionManager::Internal diff --git a/src/plugins/extensionmanager/remotespec.h b/src/plugins/extensionmanager/remotespec.h new file mode 100644 index 00000000000..b9bac9b1197 --- /dev/null +++ b/src/plugins/extensionmanager/remotespec.h @@ -0,0 +1,84 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <extensionsystem/iplugin.h> +#include <extensionsystem/pluginspec.h> + +#include <utils/hostosinfo.h> + +#include <QJsonObject> + +namespace ExtensionManager::Internal { + +class Source +{ +public: + struct Platform + { + Utils::OsType os; + Utils::OsArch architecture; + }; + QString url; + std::optional<Platform> platform; +}; + +enum class Status { + Published, + Unpublished, +}; + +class RemoteSpec : public ExtensionSystem::PluginSpec +{ +public: + QString description() const override; + QString longDescription() const override; + + bool loadLibrary() override; + bool initializePlugin() override; + bool initializeExtensions() override; + bool delayedInitialize() override; + ExtensionSystem::IPlugin::ShutdownFlag stop() override; + void kill() override; + ExtensionSystem::IPlugin *plugin() const override; + Utils::FilePath installLocation(bool inUserFolder) const override; + + Utils::Result fromJson(const QJsonObject &remoteJsonData); + + QString id() const override; + QString displayName() const override; + QString vendor() const override; + QString vendorId() const override; + + const QList<Source> sources() const; + + QList<QString> tags() const; + + QDateTime createdAt() const; + QDateTime updatedAt() const; + QDateTime releasedAt() const; + + int downloads() const; + QString icon() const; + QString smallIcon() const; + + bool isLatest() const; + // ?? QString license() const {} + Status status() const; + QString statusString() const; + QString uid() const; + + bool isPack() const; + + QJsonObject pluginObject() const; + QJsonObject packObject() const; + + QStringList packPluginIds() const; + +private: + QJsonObject m_remoteJsonData; + bool m_isPack = false; +}; + +} // namespace ExtensionManager::Internal |