forked from qt-creator/qt-creator
ExtensionSystem: Allow updating and removing plugins
Change-Id: Ie81e697c97fbfe0dc66346f42d467e1a697606c3 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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});
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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 ¤t);
|
||||
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 ¤t)
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@@ -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: {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user