ExtensionSystem: Allow updating and removing plugins

Change-Id: Ie81e697c97fbfe0dc66346f42d467e1a697606c3
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Marcus Tillmanns
2025-01-30 08:46:05 +01:00
parent e32613cfe2
commit cfda5829af
11 changed files with 391 additions and 49 deletions

View File

@@ -839,11 +839,17 @@ int main(int argc, char **argv)
// Make sure we honor the system's proxy settings
QNetworkProxyFactory::setUseSystemConfiguration(true);
// We need to install plugins before we scan for them.
PluginManager::installPluginsAfterRestart();
// Load
const QStringList pluginPaths = installPluginPaths + options.customPluginPaths;
PluginManager::setPluginPaths(
getPluginPaths() + Utils::transform(pluginPaths, &FilePath::fromUserInput));
// We need to remove plugins once we have scanned for them.
PluginManager::removePluginsAfterRestart();
QMap<QString, QString> foundAppOptions;
if (pluginArguments.size() > 1) {
QMap<QString, bool> appOptions;

View File

@@ -347,6 +347,27 @@ void PluginManager::addPlugins(const PluginSpecs &specs)
d->addPlugins(specs);
}
void PluginManager::removePluginsAfterRestart()
{
d->removePluginsAfterRestart();
}
void PluginManager::installPluginsAfterRestart()
{
d->installPluginsAfterRestart();
}
void PluginManager::removePluginOnRestart(const QString &id)
{
d->removePluginOnNextRestart(id);
}
void PluginManager::installPluginOnRestart(
const Utils::FilePath &source, const Utils::FilePath &destination)
{
d->installPluginOnNextRestart(source, destination);
}
/*!
Returns \c true if any plugin has errors even though it is enabled.
Most useful to call after loadPlugins().
@@ -1689,6 +1710,11 @@ PluginSpec *PluginManager::specForPlugin(IPlugin *plugin)
return findOrDefault(d->pluginSpecs, equal(&PluginSpec::plugin, plugin));
}
bool PluginManager::takePluginIdForRemoval(const QString &id)
{
return d->m_pluginsToRemove.remove(id);
}
static QString pluginListString(const QSet<PluginSpec *> &plugins)
{
QStringList names = Utils::transform<QList>(plugins, &PluginSpec::name);
@@ -1899,6 +1925,36 @@ static const FilePaths pluginFiles(const FilePaths &pluginPaths)
return pluginFiles;
}
bool PluginManagerPrivate::removePlugin(const QString &pluginId)
{
PluginSpec *existingSpec
= Utils::findOrDefault(pluginSpecs, Utils::equal(&PluginSpec::id, pluginId));
if (existingSpec) {
QTC_ASSERT(existingSpec->state() == PluginSpec::State::Resolved, return false);
const Result removeResult = existingSpec->removePluginFiles();
if (!removeResult) {
qCWarning(pluginLog) << "Failed to remove plugin files for" << pluginId << ":"
<< removeResult.error();
return false;
}
for (QList<PluginSpec *> &category : pluginCategories) {
category.removeOne(existingSpec);
}
if (pluginSpecs.removeOne(existingSpec)) {
delete existingSpec;
return true;
}
}
// This is used by other plugin managers like Lua that is not loaded yet.
m_pluginsToRemove << pluginId;
return true;
}
void PluginManagerPrivate::addPlugins(const PluginSpecs &specs)
{
pluginSpecs += specs;
@@ -1928,6 +1984,108 @@ void PluginManagerPrivate::addPlugins(const PluginSpecs &specs)
emit q->pluginsChanged();
}
static const char PLUGINS_TO_INSTALL_KEY[] = "PluginsToInstall";
static const char PLUGINS_TO_REMOVE_KEY[] = "PluginsToRemove";
void PluginManagerPrivate::removePluginOnNextRestart(const QString &pluginId)
{
settings->setValue(
PLUGINS_TO_REMOVE_KEY, settings->value(PLUGINS_TO_REMOVE_KEY).toStringList() << pluginId);
settings->sync();
}
static QList<QPair<FilePath, FilePath>> readPluginInstallList(QtcSettings *settings)
{
int size = settings->beginReadArray(PLUGINS_TO_INSTALL_KEY);
QList<QPair<FilePath, FilePath>> installList;
for (int i = 0; i < size; ++i) {
settings->setArrayIndex(i);
installList.append(
{FilePath::fromVariant(settings->value("src")),
FilePath::fromVariant(settings->value("dest"))});
}
settings->endArray();
return installList;
}
void PluginManagerPrivate::installPluginOnNextRestart(
const Utils::FilePath &src, const Utils::FilePath &dest)
{
const QList<QPair<FilePath, FilePath>> list = readPluginInstallList(settings)
<< qMakePair(src, dest);
settings->beginWriteArray(PLUGINS_TO_INSTALL_KEY);
for (int i = 0; i < list.size(); ++i) {
settings->setArrayIndex(i);
settings->setValue("src", list.at(i).first.toVariant());
settings->setValue("dest", list.at(i).second.toVariant());
}
settings->endArray();
settings->sync();
}
void PluginManagerPrivate::removePluginsAfterRestart()
{
const QStringList removeList = settings->value(PLUGINS_TO_REMOVE_KEY).toStringList();
for (const QString &pluginId : removeList)
removePlugin(pluginId);
settings->remove(PLUGINS_TO_REMOVE_KEY);
}
void PluginManagerPrivate::installPluginsAfterRestart()
{
QTC_CHECK(pluginSpecs.isEmpty());
QList<QPair<FilePath, FilePath>> installList = readPluginInstallList(settings);
const Utils::FilePaths pluginPaths = PluginManager::pluginPaths();
for (const auto &[src, dest] : installList) {
if (!src.exists()) {
qCWarning(pluginLog()) << "Cannot install source " << src << ", it does not exist";
continue;
}
if (dest.isDir()) {
if (auto result = dest.removeRecursively(); !result) {
qCWarning(pluginLog()) << "Failed to remove" << dest << ":" << result.error();
continue;
}
} else if (dest.isFile()) {
if (const Result result = dest.removeFile(); !result) {
qCWarning(pluginLog()) << "Failed to remove" << dest << ":" << result.error();
continue;
}
}
if (src.isFile()) {
if (!dest.createDir()) {
qCWarning(pluginLog()) << "Cannot install file" << src << "to" << dest
<< "because the destination directory cannot be created";
continue;
}
}
Utils::Result result = src.isDir() ? src.copyRecursively(dest)
: src.copyFile(dest / src.fileName());
if (!result) {
qCWarning(pluginLog())
<< "Failed to install" << src << "to" << dest << ":" << result.error();
continue;
}
result = src.isDir() ? src.removeRecursively() : src.removeFile();
if (!result)
qCWarning(pluginLog())
<< "Failed to remove the source file in" << src << ":" << result.error();
}
settings->remove(PLUGINS_TO_INSTALL_KEY);
}
/*!
\internal
*/
@@ -1954,6 +2112,13 @@ void PluginManagerPrivate::readPluginPaths()
newSpecs.append(spec->release());
}
newSpecs = Utils::filtered(newSpecs, [this](PluginSpec *spec) {
return pluginById(spec->id()) == nullptr;
});
if (newSpecs.empty())
return;
addPlugins(newSpecs);
}

View File

@@ -80,9 +80,19 @@ public:
static const QSet<PluginSpec *> pluginsRequiredByPlugin(PluginSpec *spec);
static void checkForProblematicPlugins();
static PluginSpec *specForPlugin(IPlugin *plugin);
static bool takePluginIdForRemoval(const QString &id);
static void addPlugins(const QVector<PluginSpec *> &specs);
static void reInstallPlugins();
static void removePluginOnRestart(const QString &id);
static void installPluginOnRestart(
const Utils::FilePath &source, const Utils::FilePath &destination);
static void removePluginsAfterRestart();
static void installPluginsAfterRestart();
// UI
static std::optional<QSet<PluginSpec *>> askForEnablingPlugins(
QWidget *dialogParent, const QSet<PluginSpec *> &plugins, bool enable);

View File

@@ -52,6 +52,8 @@ public:
void loadPluginsAtRuntime(const QSet<PluginSpec *> &plugins);
void addPlugins(const QVector<PluginSpec *> &specs);
bool removePlugin(const QString &pluginId);
void shutdown();
void setPluginPaths(const Utils::FilePaths &paths);
const QVector<ExtensionSystem::PluginSpec *> loadQueue();
@@ -70,6 +72,13 @@ public:
bool acceptTermsAndConditions(PluginSpec *spec);
void setAcceptTermsAndConditionsCallback(const std::function<bool(PluginSpec *)> &callback);
void readPluginPaths();
void removePluginsAfterRestart();
void installPluginsAfterRestart();
void removePluginOnNextRestart(const QString &pluginId);
void installPluginOnNextRestart(const Utils::FilePath &src, const Utils::FilePath &dest);
class TestSpec {
public:
@@ -143,12 +152,13 @@ public:
PluginManager::ProcessData m_creatorProcessData;
QSet<QString> m_pluginsToRemove;
private:
PluginManager *q;
void startDelayedInitialize();
void readPluginPaths();
bool loadQueue(PluginSpec *spec,
QVector<ExtensionSystem::PluginSpec *> &queue,
QVector<ExtensionSystem::PluginSpec *> &circularityCheckQueue);

View File

@@ -1453,4 +1453,33 @@ QList<PluginSpec *> pluginSpecsFromArchive(const Utils::FilePath &path)
return results;
}
Utils::Result PluginSpec::removePluginFiles() const
{
if (isSystemPlugin())
return Result::Error(Tr::tr("Cannot remove system plugins."));
// Try to figure out where we are ...
const Utils::FilePaths pluginPaths = PluginManager::pluginPaths();
for (const FilePath &pluginPath : pluginPaths) {
if (location().isChildOf(pluginPath)) {
const FilePath rootFolder = location().relativeChildPath(pluginPath);
if (rootFolder.isEmpty())
return Result::Error(Tr::tr("Could not determine root folder."));
const FilePath pathToDelete = pluginPath
/ rootFolder.pathComponents().first().toString();
return pathToDelete.removeRecursively();
}
}
return filePath().removeFile();
}
bool PluginSpec::isSystemPlugin() const
{
return !filePath().isChildOf(appInfo().userPluginsRoot)
&& !filePath().isChildOf(appInfo().userLuaPlugins);
}
} // namespace ExtensionSystem

View File

@@ -161,6 +161,9 @@ public:
virtual Utils::FilePath installLocation(bool inUserFolder) const = 0;
virtual Utils::Result removePluginFiles() const;
virtual bool isSystemPlugin() const;
protected:
virtual void setEnabledByDefault(bool value);
virtual void setEnabledIndirectly(bool value);

View File

@@ -51,6 +51,7 @@ struct Data
bool installIntoApplication = false;
std::unique_ptr<PluginSpec> pluginSpec = nullptr;
bool loadImmediately = false;
bool prepareForUpdate = false;
};
static bool hasLibSuffix(const FilePath &path)
@@ -127,13 +128,20 @@ public:
using CheckResult = expected_str<PluginSpec *>;
static Result checkPlugin(PluginSpec *spec)
static Result checkPlugin(PluginSpec *spec, bool update)
{
if (Utils::anyOf(PluginManager::plugins(), [spec](PluginSpec *other) {
const bool pluginAlreadyExists
= Utils::anyOf(PluginManager::plugins(), [spec](PluginSpec *other) {
return other->id() == spec->id();
}))
});
if (!update && pluginAlreadyExists) {
return Result::Error(
Tr::tr("A plugin with ID \"%1\" is already installed.").arg(spec->id()));
} else if (update && !pluginAlreadyExists) {
return Result::Error(Tr::tr("No plugin with ID \"%1\" is installed.").arg(spec->id()));
}
if (!spec->resolveDependencies(PluginManager::plugins())) {
return Result::Error(
Tr::tr("Plugin failed to resolve dependencies:") + " " + spec->errorString());
@@ -142,18 +150,18 @@ static Result checkPlugin(PluginSpec *spec)
}
static expected_str<std::unique_ptr<PluginSpec>> checkPlugin(
expected_str<std::unique_ptr<PluginSpec>> spec)
expected_str<std::unique_ptr<PluginSpec>> spec, bool update)
{
if (!spec)
return spec;
const Result ok = checkPlugin(spec->get());
const Result ok = checkPlugin(spec->get(), update);
if (ok)
return spec;
return Utils::make_unexpected(ok.error());
}
// Async. Result is set if any issue was found.
void checkContents(QPromise<CheckResult> &promise, const FilePath &tempDir)
void checkContents(QPromise<CheckResult> &promise, const FilePath &tempDir, bool update)
{
QList<PluginSpec *> plugins = pluginSpecsFromArchive(tempDir);
if (plugins.isEmpty()) {
@@ -167,7 +175,7 @@ void checkContents(QPromise<CheckResult> &promise, const FilePath &tempDir)
}
PluginSpec *plugin = plugins.front();
const Result ok = checkPlugin(plugin);
const Result ok = checkPlugin(plugin, update);
if (!ok) {
promise.addResult(Utils::make_unexpected(ok.error()));
delete plugin;
@@ -214,8 +222,8 @@ public:
emit completeChanged();
if (hasLibSuffix(m_data->sourcePath)) {
m_cancelButton->setVisible(false);
expected_str<std::unique_ptr<PluginSpec>> spec = checkPlugin(
readCppPluginSpec(m_data->sourcePath));
expected_str<std::unique_ptr<PluginSpec>> spec
= checkPlugin(readCppPluginSpec(m_data->sourcePath), m_data->prepareForUpdate);
if (!spec) {
m_label->setType(InfoLabel::Error);
m_label->setText(spec.error());
@@ -228,8 +236,18 @@ public:
return;
}
m_tempDir = std::make_unique<TemporaryDirectory>("plugininstall");
m_data->extractedPath = m_tempDir->path();
FilePath tmpDirBase;
if (m_data->prepareForUpdate)
tmpDirBase = appInfo().userResources / "install-prep-area";
else
tmpDirBase = TemporaryDirectory::masterDirectoryFilePath();
tmpDirBase.ensureWritableDir();
m_tempDir = std::make_unique<QTemporaryDir>((tmpDirBase / "plugininstall").path());
m_tempDir->setAutoRemove(false);
m_data->extractedPath = FilePath::fromString(m_tempDir->path());
m_label->setText(Tr::tr("Checking archive..."));
m_label->setType(InfoLabel::None);
@@ -244,7 +262,7 @@ public:
const auto onUnarchiverSetup = [this, sourceAndCommand](Unarchiver &unarchiver) {
unarchiver.setSourceAndCommand(*sourceAndCommand);
unarchiver.setDestDir(m_tempDir->path());
unarchiver.setDestDir(m_data->extractedPath);
connect(&unarchiver, &Unarchiver::outputReceived, this, [this](const QString &output) {
m_output->append(output);
});
@@ -255,10 +273,11 @@ public:
};
const auto onCheckerSetup = [this](Async<CheckResult> &async) {
if (!m_tempDir)
if (!m_data->extractedPath.exists())
return SetupResult::StopWithError;
async.setConcurrentCallData(checkContents, m_tempDir->path());
async.setConcurrentCallData(
checkContents, m_data->extractedPath, m_data->prepareForUpdate);
return SetupResult::Continue;
};
const auto onCheckerDone = [this](const Async<CheckResult> &async) {
@@ -289,8 +308,11 @@ public:
{
// back button pressed
m_taskTreeRunner.reset();
if (m_tempDir) {
m_tempDir->remove();
m_tempDir.reset();
}
}
bool isComplete() const final { return m_isComplete; }
@@ -301,7 +323,7 @@ public:
return WizardPage::nextId();
}
std::unique_ptr<TemporaryDirectory> m_tempDir;
std::unique_ptr<QTemporaryDir> m_tempDir;
TaskTreeRunner m_taskTreeRunner;
InfoLabel *m_label = nullptr;
QPushButton *m_cancelButton = nullptr;
@@ -489,7 +511,13 @@ static bool copyPluginFile(const FilePath &src, const FilePath &dest)
return false;
destFile.removeFile();
}
dest.parentDir().ensureWritableDir();
if (!destFile.parentDir().ensureWritableDir()) {
QMessageBox::warning(
ICore::dialogParent(),
Tr::tr("Failed to Write File"),
Tr::tr("Failed to create directory \"%1\".").arg(dest.toUserOutput()));
return false;
}
if (!src.copyFile(destFile)) {
QMessageBox::warning(ICore::dialogParent(),
Tr::tr("Failed to Write File"),
@@ -500,12 +528,18 @@ static bool copyPluginFile(const FilePath &src, const FilePath &dest)
return true;
}
InstallResult executePluginInstallWizard(const FilePath &archive)
QString extensionId(PluginSpec *spec)
{
return spec->vendorId() + "." + spec->id();
}
InstallResult executePluginInstallWizard(const FilePath &archive, bool prepareForUpdate)
{
Wizard wizard;
wizard.setWindowTitle(Tr::tr("Install Plugin"));
Data data;
data.prepareForUpdate = prepareForUpdate;
if (archive.isEmpty()) {
auto filePage = new SourcePage(&data, &wizard);
@@ -526,10 +560,23 @@ InstallResult executePluginInstallWizard(const FilePath &archive)
auto summaryPage = new SummaryPage(&data, &wizard);
wizard.addPage(summaryPage);
auto install = [&wizard, &data]() {
auto install = [&wizard, &data, prepareForUpdate]() {
if (wizard.exec()) {
const FilePath installPath = data.pluginSpec->installLocation(
!data.installIntoApplication);
!data.installIntoApplication)
/ extensionId(data.pluginSpec.get());
if (prepareForUpdate) {
if (hasLibSuffix(data.sourcePath)) {
ExtensionSystem::PluginManager::installPluginOnRestart(
data.pluginSpec->filePath(), installPath);
} else {
ExtensionSystem::PluginManager::installPluginOnRestart(
data.extractedPath, installPath);
}
return true;
}
if (hasLibSuffix(data.sourcePath)) {
if (!copyPluginFile(data.sourcePath, installPath))
return false;
@@ -568,6 +615,9 @@ InstallResult executePluginInstallWizard(const FilePath &archive)
// so we can safely set them as accepted here.
PluginManager::instance()->setTermsAndConditionsAccepted(data.pluginSpec.get());
if (prepareForUpdate)
return InstallResult::NeedsRestart;
auto spec = data.pluginSpec.release();
PluginManager::addPlugins({spec});

View File

@@ -17,6 +17,7 @@ enum class InstallResult {
NeedsRestart,
};
CORE_EXPORT InstallResult executePluginInstallWizard(const Utils::FilePath &archive = {});
CORE_EXPORT InstallResult
executePluginInstallWizard(const Utils::FilePath &archive = {}, bool prepareForUpdate = false);
} // namespace Core

View File

@@ -25,6 +25,7 @@
#include <solutions/tasking/tasktreerunner.h>
#include <utils/algorithm.h>
#include <utils/appinfo.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <utils/icon.h>
@@ -87,6 +88,21 @@ static QWidget *toScrollableColumn(QWidget *widget)
return scrollArea;
};
const char kRestartSetting[] = "RestartAfterPluginEnabledChanged";
static void requestRestart()
{
if (ICore::infoBar()->canInfoBeAdded(kRestartSetting)) {
Utils::InfoBarEntry
info(kRestartSetting, Core::Tr::tr("Plugin changes will take effect after restart."));
info.addCustomButton(Tr::tr("Restart Now"), [] {
ICore::infoBar()->removeInfo(kRestartSetting);
QTimer::singleShot(0, ICore::instance(), &ICore::restart);
});
ICore::infoBar()->addInfo(info);
}
}
class CollapsingWidget : public QWidget
{
public:
@@ -153,6 +169,24 @@ public:
installButton = new Button(Tr::tr("Install..."), Button::LargePrimary);
installButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
installButton->hide();
connect(
installButton,
&QAbstractButton::pressed,
this,
&HeadingWidget::pluginInstallationRequested);
removeButton = new Button(Tr::tr("Remove ..."), Button::SmallSecondary);
removeButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
removeButton->hide();
connect(removeButton, &QAbstractButton::pressed, this, [this]() {
ExtensionSystem::PluginManager::removePluginOnRestart(m_currentPluginId);
requestRestart();
});
updateButton = new Button(Tr::tr("Update ..."), Button::LargePrimary);
updateButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
updateButton->hide();
connect(updateButton, &QAbstractButton::pressed, this, &HeadingWidget::pluginUpdateRequested);
using namespace Layouting;
Row {
@@ -181,6 +215,8 @@ public:
},
Column {
installButton,
updateButton,
removeButton,
st,
},
noMargin, spacing(SpacingTokens::ExPaddingGapL),
@@ -189,8 +225,6 @@ public:
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
m_dlCountItems->setVisible(false);
connect(installButton, &QAbstractButton::pressed,
this, &HeadingWidget::pluginInstallationRequested);
connect(m_vendor, &QAbstractButton::pressed, this, [this]() {
emit vendorClicked(m_currentVendor);
});
@@ -203,6 +237,8 @@ public:
if (!current.isValid())
return;
m_currentPluginId = current.data(RoleId).toString();
m_icon->setPixmap(itemIcon(current, SizeBig));
const QString name = current.data(RoleName).toString();
@@ -220,17 +256,27 @@ public:
const QString description = current.data(RoleDescriptionShort).toString();
m_details->setText(description);
ExtensionSystem::PluginSpec *pluginSpec = pluginSpecForId(current.data(RoleId).toString());
const ItemType itemType = current.data(RoleItemType).value<ItemType>();
const bool isPack = itemType == ItemTypePack;
const bool isRemotePlugin = !(isPack || pluginSpecForId(current.data(RoleId).toString()));
const bool isRemotePlugin = !(isPack || pluginSpec);
const QString downloadUrl = current.data(RoleDownloadUrl).toString();
removeButton->setVisible(!isRemotePlugin && pluginSpec && !pluginSpec->isSystemPlugin());
installButton->setVisible(isRemotePlugin && !downloadUrl.isEmpty());
if (installButton->isVisible())
installButton->setToolTip(downloadUrl);
updateButton->setVisible(
pluginSpec
&& ExtensionSystem::PluginSpec::versionCompare(
pluginSpec->version(), current.data(RoleVersion).toString())
< 0);
}
signals:
void pluginInstallationRequested();
void pluginUpdateRequested();
void vendorClicked(const QString &vendor);
private:
@@ -243,24 +289,12 @@ private:
QWidget *m_dlCountItems;
QLabel *m_details;
QAbstractButton *installButton;
QAbstractButton *removeButton;
QAbstractButton *updateButton;
QString m_currentVendor;
QString m_currentPluginId;
};
const char kRestartSetting[] = "RestartAfterPluginEnabledChanged";
static void requestRestart()
{
if (ICore::infoBar()->canInfoBeAdded(kRestartSetting)) {
Utils::InfoBarEntry
info(kRestartSetting, Core::Tr::tr("Plugin changes will take effect after restart."));
info.addCustomButton(Tr::tr("Restart Now"), [] {
ICore::infoBar()->removeInfo(kRestartSetting);
QTimer::singleShot(0, ICore::instance(), &ICore::restart);
});
ICore::infoBar()->addInfo(info);
}
}
class PluginStatusWidget : public QWidget
{
public:
@@ -394,7 +428,7 @@ public:
private:
void updateView(const QModelIndex &current);
void fetchAndInstallPlugin(const QUrl &url, const QString &id);
void fetchAndInstallPlugin(const QUrl &url, const QString &id, bool update);
QString m_currentItemName;
ExtensionsModel *m_extensionModel;
@@ -562,7 +596,10 @@ ExtensionManagerWidget::ExtensionManagerWidget()
m_secondaryDescriptionWidget->setWidth(secondaryDescriptionWidth);
});
connect(m_headingWidget, &HeadingWidget::pluginInstallationRequested, this, [this]() {
fetchAndInstallPlugin(QUrl::fromUserInput(m_currentDownloadUrl), m_currentId);
fetchAndInstallPlugin(QUrl::fromUserInput(m_currentDownloadUrl), m_currentId, false);
});
connect(m_headingWidget, &HeadingWidget::pluginUpdateRequested, this, [this]() {
fetchAndInstallPlugin(QUrl::fromUserInput(m_currentDownloadUrl), m_currentId, true);
});
connect(m_tags, &TagList::tagSelected, m_extensionBrowser, &ExtensionsBrowser::setFilter);
connect(m_headingWidget, &HeadingWidget::vendorClicked,
@@ -649,7 +686,7 @@ void ExtensionManagerWidget::updateView(const QModelIndex &current)
}
}
void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url, const QString &id)
void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url, const QString &id, bool update)
{
using namespace Tasking;
@@ -720,7 +757,7 @@ void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url, const QStrin
}
};
const auto onPluginInstallation = [storage]() {
const auto onPluginInstallation = [storage, update]() {
if (storage->packageData.isEmpty())
return false;
const FilePath source = FilePath::fromUrl(storage->url);
@@ -730,7 +767,7 @@ void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url, const QStrin
saver.write(storage->packageData);
if (saver.finalize(ICore::dialogParent())) {
auto result = executePluginInstallWizard(saver.filePath());
auto result = executePluginInstallWizard(saver.filePath(), update);
switch (result) {
case InstallResult::Success:
return true;

View File

@@ -48,7 +48,7 @@ public:
void ExtensionsModelPrivate::addUnlistedLocalPlugins()
{
QStringList responseExtensions;
QSet<QString> responseExtensions;
for (const QJsonValueConstRef &responseItem : qAsConst(responseItems))
responseExtensions << responseItem.toObject().value("id").toString();
@@ -124,6 +124,8 @@ QVariant ExtensionsModelPrivate::dataFromRemotePlugin(const QJsonObject &json, i
}
break;
}
case RoleVersion:
return metaData.value("Version");
case RoleItemType:
return ItemTypeExtension;
case RoleDescriptionLong: {

View File

@@ -416,10 +416,26 @@ public:
FilePaths folders = path.dirEntries(FileFilter({}, QDir::Dirs | QDir::NoDotAndDotDot));
for (const FilePath &folder : folders) {
const FilePath script = folder / (folder.baseName() + ".lua");
if (!script.exists())
FilePath script = folder / (folder.baseName() + ".lua");
if (!script.exists()) {
FilePaths contents = folder.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot);
if (contents.empty())
continue;
for (const FilePath &subfolder : contents) {
script = subfolder / (subfolder.baseName() + ".lua");
if (!script.exists()) {
script.clear();
continue;
}
break;
}
}
if (script.isEmpty()) {
continue;
}
const expected_str<LuaPluginSpec *> result = loadPlugin(script);
if (!result) {
@@ -430,6 +446,19 @@ public:
continue;
}
if (PluginManager::takePluginIdForRemoval((*result)->id())) {
auto removeResult = (*result)->removePluginFiles();
if (!removeResult) {
qWarning() << "Failed to remove plugin files" << script << ":"
<< removeResult.error();
MessageManager::writeFlashing(
Tr::tr("Failed to remove plugin files of %1: %2")
.arg((*result)->id())
.arg(removeResult.error()));
}
continue;
}
plugins.insert(*result);
}
}