From bda51986c1443c2d53b92a9d4dea4e81e7ac4032 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Thu, 3 Aug 2023 16:35:55 -0400 Subject: [PATCH] ENH: Add extensions manager API for downloading and installing an extension (#7145) Allow the user to download and install an extension along with its dependencies from the extensions server. By default, it will prompt the user to confirm the installation and restart of the application after installation. When the testing mode is enabled, the function installs the extension and its dependencies without any confirmation dialogs. In this case, the application is not automatically restarted. To skip the confirmation dialogs during installation without enabling testing mode, simply call setInteractive(false) on the extensions manager model before invoking this function. To prevent the application from automatically restarting after the installation is completed, set the \a restart parameter to false. Co-authored-by: SET --- Base/QTCore/qSlicerExtensionsManagerModel.cxx | 79 +++++++++++++++++++ Base/QTCore/qSlicerExtensionsManagerModel.h | 26 ++++++ 2 files changed, 105 insertions(+) diff --git a/Base/QTCore/qSlicerExtensionsManagerModel.cxx b/Base/QTCore/qSlicerExtensionsManagerModel.cxx index ebcd656dc19..435778e7b18 100644 --- a/Base/QTCore/qSlicerExtensionsManagerModel.cxx +++ b/Base/QTCore/qSlicerExtensionsManagerModel.cxx @@ -1713,6 +1713,85 @@ bool qSlicerExtensionsManagerModel::downloadAndInstallExtensionByName(const QStr return true; } +//----------------------------------------------------------------------------- +void qSlicerExtensionsManagerModel::installExtensionFromServer(const QString& extensionName, bool restart) +{ + Q_D(qSlicerExtensionsManagerModel); + + if (this->isExtensionInstalled(extensionName)) + { + return; + } + + bool isTestingEnabled = qSlicerCoreApplication::testAttribute(qSlicerCoreApplication::AA_EnableTesting); + + // Handle installation confirmation + bool installationConfirmed = false; + if (isTestingEnabled) + { + installationConfirmed = true; + qDebug() << "Installing the extension(s) without asking for confirmation (testing mode is enabled)"; + } + else if (!this->interactive()) + { + installationConfirmed = true; + qDebug() << "Installing the extension(s) without asking for confirmation (interactive mode is disabled)"; + } + else + { + QString message = tr("Do you want to install '%1' now?").arg(extensionName); + QMessageBox::StandardButton answer = QMessageBox::question(nullptr, tr("Install extension ?"), message); + installationConfirmed = (answer == QMessageBox::StandardButton::Yes); + } + if (!installationConfirmed) + { + return; + } + + // Ensure extension metadata is retrieved from the server or cache. + if (!this->updateExtensionsMetadataFromServer(/* force= */ true, /* waitForCompletion= */ true)) + { + return; + } + + // Install extension and its dependencies + if (!this->downloadAndInstallExtensionByName(extensionName, /* installDependencies= */ true, /* waitForCompletion= */ true)) + { + d->critical(tr("Failed to install %1 extension").arg(extensionName)); + return; + } + + if (!restart) + { + return; + } + + // Handle restart confirmation + bool restartConfirmed = false; + if (isTestingEnabled) + { + restartConfirmed = false; + qDebug() << "Skipping application restart (testing mode is enabled)"; + } + else if (!this->interactive()) + { + restartConfirmed = true; + qDebug() << "Restarting the application without asking for confirmation (interactive mode is disabled)"; + } + else + { + QString message = tr("Extension %1 has been installed from server.").arg(extensionName); + message + "\n\n"; + message += tr("Slicer must be restarted. Do you want to restart now ?"); + QMessageBox::StandardButton answer = QMessageBox::question(nullptr, tr("Restart slicer ?"), message); + restartConfirmed = (answer == QMessageBox::StandardButton::Yes); + } + if (restartConfirmed) + { + qSlicerCoreApplication::application()->restart(); + } +} + // -------------------------------------------------------------------------- void qSlicerExtensionsManagerModel::onInstallDownloadProgress( qSlicerExtensionDownloadTask* task, qint64 received, qint64 total) diff --git a/Base/QTCore/qSlicerExtensionsManagerModel.h b/Base/QTCore/qSlicerExtensionsManagerModel.h index 9be905f1b89..bd8e577ded9 100644 --- a/Base/QTCore/qSlicerExtensionsManagerModel.h +++ b/Base/QTCore/qSlicerExtensionsManagerModel.h @@ -380,6 +380,32 @@ public slots: /// \sa installExtension, scheduleExtensionForUninstall, uninstallScheduledExtensions bool downloadAndInstallExtensionByName(const QString& extensionName, bool installDependencies = true, bool waitForCompletion = false); + /// \brief Download and install an extension from the extensions server. + /// + /// This function allows the user to download and install an extension + /// along with its dependencies from the extensions server. By default, + /// it will prompt the user to confirm the installation and restart of + /// the application after installation. + /// + /// When the testing mode is enabled, the function installs the extension + /// and its dependencies without any confirmation dialogs. In this case, + /// the application is not automatically restarted. + /// + /// If you wish to skip the confirmation dialogs during installation + /// without enabling testing mode, you can call setInteractive(false) on + /// the extensions manager model before invoking this function. + /// + /// To prevent the application from automatically restarting after the + /// installation is completed, set the \a restart parameter to false. + /// + /// \param extensionName The name of the extension to be installed. + /// \param restart Set to false to prevent automatic application restart (default: true). + /// + /// \sa setInteractive + /// \sa isExtensionInstalled, installExtension, updateExtensionsMetadataFromServer, downloadAndInstallExtensionByName + /// \sa qSlicerCoreApplication::testAttribute, qSlicerCoreApplication::AA_EnableTesting, qSlicerCoreApplication::restart + void installExtensionFromServer(const QString& extensionName, bool restart = true); + /// \brief Schedule \a extensionName of uninstall /// Tell the application to uninstall \a extensionName when it will restart /// An extension scheduled for uninstall can be effectively uninstalled by calling