From 9db61119aa4f58de7f4b5d56ec09180d848a906a Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:11:27 +0100 Subject: [PATCH] feat: Add check plugin update and dialog for update --- CMakeLists.txt | 1 + UpdateStatusWidget.cpp | 72 +++++++++++++ UpdateStatusWidget.hpp | 47 ++++++++ qodeassist.cpp | 60 ++++++++--- settings/CMakeLists.txt | 2 + settings/GeneralSettings.cpp | 26 +++-- settings/GeneralSettings.hpp | 2 + settings/PluginUpdater.cpp | 192 +++++++++++++++++++++++++++++++++ settings/PluginUpdater.hpp | 72 +++++++++++++ settings/SettingsConstants.hpp | 2 + settings/SettingsTr.hpp | 4 + settings/UpdateDialog.cpp | 174 ++++++++++++++++++++++++++++++ settings/UpdateDialog.hpp | 63 +++++++++++ 13 files changed, 693 insertions(+), 24 deletions(-) create mode 100644 UpdateStatusWidget.cpp create mode 100644 UpdateStatusWidget.hpp create mode 100644 settings/PluginUpdater.cpp create mode 100644 settings/PluginUpdater.hpp create mode 100644 settings/UpdateDialog.cpp create mode 100644 settings/UpdateDialog.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4081f75..8576f60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,4 +68,5 @@ add_qtc_plugin(QodeAssist chat/NavigationPanel.hpp chat/NavigationPanel.cpp ConfigurationManager.hpp ConfigurationManager.cpp CodeHandler.hpp CodeHandler.cpp + UpdateStatusWidget.hpp UpdateStatusWidget.cpp ) diff --git a/UpdateStatusWidget.cpp b/UpdateStatusWidget.cpp new file mode 100644 index 0000000..10c6b44 --- /dev/null +++ b/UpdateStatusWidget.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#include "UpdateStatusWidget.hpp" + +namespace QodeAssist { + +UpdateStatusWidget::UpdateStatusWidget(QWidget *parent) + : QFrame(parent) +{ + setFrameStyle(QFrame::NoFrame); + + auto layout = new QHBoxLayout(this); + layout->setContentsMargins(4, 0, 4, 0); + layout->setSpacing(4); + + m_actionButton = new QToolButton(this); + m_actionButton->setToolButtonStyle(Qt::ToolButtonIconOnly); + + m_versionLabel = new QLabel(this); + m_versionLabel->setVisible(false); + + m_updateButton = new QPushButton(tr("Update"), this); + m_updateButton->setVisible(false); + m_updateButton->setStyleSheet("QPushButton { padding: 2px 8px; }"); + + layout->addWidget(m_actionButton); + layout->addWidget(m_versionLabel); + layout->addWidget(m_updateButton); +} + +void UpdateStatusWidget::setDefaultAction(QAction *action) +{ + m_actionButton->setDefaultAction(action); +} + +void UpdateStatusWidget::showUpdateAvailable(const QString &version) +{ + m_versionLabel->setText(tr("new version: v%1").arg(version)); + m_versionLabel->setVisible(true); + m_updateButton->setVisible(true); + m_updateButton->setToolTip(tr("Update QodeAssist to version %1").arg(version)); +} + +void UpdateStatusWidget::hideUpdateInfo() +{ + m_versionLabel->setVisible(false); + m_updateButton->setVisible(false); +} + +QPushButton *UpdateStatusWidget::updateButton() const +{ + return m_updateButton; +} + +} // namespace QodeAssist diff --git a/UpdateStatusWidget.hpp b/UpdateStatusWidget.hpp new file mode 100644 index 0000000..7879c22 --- /dev/null +++ b/UpdateStatusWidget.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace QodeAssist { + +class UpdateStatusWidget : public QFrame +{ + Q_OBJECT +public: + explicit UpdateStatusWidget(QWidget *parent = nullptr); + + void setDefaultAction(QAction *action); + void showUpdateAvailable(const QString &version); + void hideUpdateInfo(); + + QPushButton *updateButton() const; + +private: + QToolButton *m_actionButton; + QLabel *m_versionLabel; + QPushButton *m_updateButton; +}; +} // namespace QodeAssist diff --git a/qodeassist.cpp b/qodeassist.cpp index 213d7e5..7ff2c6b 100644 --- a/qodeassist.cpp +++ b/qodeassist.cpp @@ -19,6 +19,8 @@ #include "QodeAssistConstants.hpp" #include "QodeAssisttr.h" +#include "settings/PluginUpdater.hpp" +#include "settings/UpdateDialog.hpp" #include #include @@ -32,19 +34,21 @@ #include #include +#include +#include #include #include #include #include -#include -#include #include "ConfigurationManager.hpp" #include "QodeAssistClient.hpp" #include "chat/ChatOutputPane.h" #include "chat/NavigationPanel.hpp" +#include "settings/GeneralSettings.hpp" #include "settings/ProjectSettingsPanel.hpp" +#include "UpdateStatusWidget.hpp" #include "providers/Providers.hpp" #include "templates/Templates.hpp" @@ -61,8 +65,8 @@ class QodeAssistPlugin final : public ExtensionSystem::IPlugin public: QodeAssistPlugin() - { - } + : m_updater(new PluginUpdater(this)) + {} ~QodeAssistPlugin() final { @@ -96,33 +100,36 @@ public: } }); - auto toggleButton = new QToolButton; - toggleButton->setDefaultAction(requestAction.contextAction()); - StatusBarManager::addStatusBarWidget(toggleButton, StatusBarManager::RightCorner); + m_statusWidget = new UpdateStatusWidget; + m_statusWidget->setDefaultAction(requestAction.contextAction()); + StatusBarManager::addStatusBarWidget(m_statusWidget, StatusBarManager::RightCorner); + + connect(m_statusWidget->updateButton(), &QPushButton::clicked, this, [this]() { + UpdateDialog::checkForUpdatesAndShow(Core::ICore::mainWindow()); + }); m_chatOutputPane = new Chat::ChatOutputPane(this); m_navigationPanel = new Chat::NavigationPanel(); Settings::setupProjectPanel(); - ConfigurationManager::instance().init(); + + if (Settings::generalSettings().enableCheckUpdate()) { + QTimer::singleShot(3000, this, &QodeAssistPlugin::checkForUpdates); + } } - void extensionsInitialized() final - { - } + void extensionsInitialized() final {} void restartClient() { LanguageClient::LanguageClientManager::shutdownClient(m_qodeAssistClient); - m_qodeAssistClient = new QodeAssistClient(); } bool delayedInitialize() final { restartClient(); - return true; } @@ -130,17 +137,36 @@ public: { if (!m_qodeAssistClient) return SynchronousShutdown; - connect(m_qodeAssistClient, - &QObject::destroyed, - this, - &IPlugin::asynchronousShutdownFinished); + connect(m_qodeAssistClient, &QObject::destroyed, this, &IPlugin::asynchronousShutdownFinished); return AsynchronousShutdown; } private: + void checkForUpdates() + { + connect( + m_updater, + &PluginUpdater::updateCheckFinished, + this, + &QodeAssistPlugin::handleUpdateCheckResult, + Qt::UniqueConnection); + m_updater->checkForUpdates(); + } + + void handleUpdateCheckResult(const PluginUpdater::UpdateInfo &info) + { + if (!info.isUpdateAvailable) + return; + + if (m_statusWidget) + m_statusWidget->showUpdateAvailable(info.version); + } + QPointer m_qodeAssistClient; QPointer m_chatOutputPane; QPointer m_navigationPanel; + QPointer m_updater; + UpdateStatusWidget *m_statusWidget{nullptr}; }; } // namespace QodeAssist::Internal diff --git a/settings/CMakeLists.txt b/settings/CMakeLists.txt index 65cd398..be61c3b 100644 --- a/settings/CMakeLists.txt +++ b/settings/CMakeLists.txt @@ -11,6 +11,8 @@ add_library(QodeAssistSettings STATIC ProjectSettings.hpp ProjectSettings.cpp ProjectSettingsPanel.hpp ProjectSettingsPanel.cpp ProviderSettings.hpp ProviderSettings.cpp + PluginUpdater.hpp PluginUpdater.cpp + UpdateDialog.hpp UpdateDialog.cpp ) target_link_libraries(QodeAssistSettings diff --git a/settings/GeneralSettings.cpp b/settings/GeneralSettings.cpp index c4cd58b..bd03a71 100644 --- a/settings/GeneralSettings.cpp +++ b/settings/GeneralSettings.cpp @@ -37,6 +37,7 @@ #include "SettingsDialog.hpp" #include "SettingsTr.hpp" #include "SettingsUtils.hpp" +#include "UpdateDialog.hpp" namespace QodeAssist::Settings { @@ -60,7 +61,12 @@ GeneralSettings::GeneralSettings() enableLogging.setLabelText(TrConstants::ENABLE_LOG); enableLogging.setDefaultValue(false); + enableCheckUpdate.setSettingsKey(Constants::ENABLE_CHECK_UPDATE); + enableCheckUpdate.setLabelText(TrConstants::ENABLE_CHECK_UPDATE_ON_START); + enableCheckUpdate.setDefaultValue(true); + resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS; + checkUpdate.m_buttonText = TrConstants::CHECK_UPDATE; initStringAspect(ccProvider, Constants::CC_PROVIDER, TrConstants::PROVIDER, "Ollama"); ccProvider.setReadOnly(true); @@ -129,13 +135,15 @@ GeneralSettings::GeneralSettings() auto ccGroup = Group{title(TrConstants::CODE_COMPLETION), ccGrid}; auto caGroup = Group{title(TrConstants::CHAT_ASSISTANT), caGrid}; - auto rootLayout = Column{Row{enableQodeAssist, Stretch{1}, resetToDefaults}, - Row{enableLogging, Stretch{1}}, - Space{8}, - ccGroup, - Space{8}, - caGroup, - Stretch{1}}; + auto rootLayout = Column{ + Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}}, + Row{enableLogging, Stretch{1}}, + Row{enableCheckUpdate, Stretch{1}}, + Space{8}, + ccGroup, + Space{8}, + caGroup, + Stretch{1}}; return rootLayout; }); @@ -295,6 +303,9 @@ void GeneralSettings::setupConnections() Logger::instance().setLoggingEnabled(enableLogging.volatileValue()); }); connect(&resetToDefaults, &ButtonAspect::clicked, this, &GeneralSettings::resetPageToDefaults); + connect(&checkUpdate, &ButtonAspect::clicked, this, [this]() { + QodeAssist::UpdateDialog::checkForUpdatesAndShow(Core::ICore::dialogParent()); + }); } void GeneralSettings::resetPageToDefaults() @@ -316,6 +327,7 @@ void GeneralSettings::resetPageToDefaults() resetAspect(caModel); resetAspect(caTemplate); resetAspect(caUrl); + resetAspect(enableCheckUpdate); writeSettings(); } } diff --git a/settings/GeneralSettings.hpp b/settings/GeneralSettings.hpp index a92dbe8..7e44278 100644 --- a/settings/GeneralSettings.hpp +++ b/settings/GeneralSettings.hpp @@ -35,6 +35,8 @@ public: Utils::BoolAspect enableQodeAssist{this}; Utils::BoolAspect enableLogging{this}; + Utils::BoolAspect enableCheckUpdate{this}; + ButtonAspect checkUpdate{this}; ButtonAspect resetToDefaults{this}; // code completion setttings diff --git a/settings/PluginUpdater.cpp b/settings/PluginUpdater.cpp new file mode 100644 index 0000000..e84c669 --- /dev/null +++ b/settings/PluginUpdater.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2024 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#include "PluginUpdater.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace QodeAssist { + +PluginUpdater::PluginUpdater(QObject *parent) + : QObject(parent) + , m_networkManager(new QNetworkAccessManager(this)) +{} + +void PluginUpdater::checkForUpdates() +{ + if (m_isCheckingUpdate) + return; + + m_isCheckingUpdate = true; + QNetworkRequest request((QUrl(getUpdateUrl()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QNetworkReply *reply = m_networkManager->get(request); + + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + handleUpdateResponse(reply); + m_isCheckingUpdate = false; + reply->deleteLater(); + }); +} + +void PluginUpdater::handleUpdateResponse(QNetworkReply *reply) +{ + UpdateInfo info; + + if (reply->error() != QNetworkReply::NoError) { + emit downloadError(reply->errorString()); + return; + } + + QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); + QJsonObject obj = doc.object(); + + info.version = obj["tag_name"].toString(); + if (info.version.startsWith('v')) + info.version.remove(0, 1); + + QString qtcVersionStr = Core::ICore::versionString().split(' ').last(); + QVersionNumber qtcVersion = QVersionNumber::fromString(qtcVersionStr); + info.currentIdeVersion = qtcVersionStr; + + auto assets = obj["assets"].toArray(); + for (const auto &asset : assets) { + QString name = asset.toObject()["name"].toString(); + + if (name.startsWith("QodeAssist-")) { + QString assetVersionStr = name.section('-', 1, 1); + QVersionNumber assetVersion = QVersionNumber::fromString(assetVersionStr); + info.targetIdeVersion = assetVersionStr; + + if (assetVersion != qtcVersion) { + continue; + } + +#if defined(Q_OS_WIN) + if (name.contains("Windows")) +#elif defined(Q_OS_MACOS) + if (name.contains("macOS")) +#else + if (name.contains("Linux")) +#endif + { + info.downloadUrl = asset.toObject()["browser_download_url"].toString(); + info.fileName = name; + break; + } + } + } + + if (info.downloadUrl.isEmpty()) { + info.incompatibleIdeVersion = true; + emit updateCheckFinished(info); + return; + } + + info.changeLog = obj["body"].toString(); + info.isUpdateAvailable = QVersionNumber::fromString(info.version) + > QVersionNumber::fromString(currentVersion()); + + m_lastUpdateInfo = info; + emit updateCheckFinished(info); +} + +void PluginUpdater::downloadUpdate(const QString &url) +{ + QNetworkRequest request(url); + QNetworkReply *reply = m_networkManager->get(request); + + connect(reply, &QNetworkReply::downloadProgress, this, &PluginUpdater::handleDownloadProgress); + connect(reply, &QNetworkReply::finished, this, &PluginUpdater::handleDownloadFinished); +} + +QString PluginUpdater::currentVersion() const +{ + const auto pluginSpecs = ExtensionSystem::PluginManager::plugins(); + for (const ExtensionSystem::PluginSpec *spec : pluginSpecs) { + if (spec->name() == QLatin1String("QodeAssist")) + return spec->version(); + } + return QString(); +} + +bool PluginUpdater::isUpdateAvailable() const +{ + return m_lastUpdateInfo.isUpdateAvailable; +} + +QString PluginUpdater::getUpdateUrl() const +{ + return "https://api.github.com/repos/Palm1r/qodeassist/releases/latest"; +} + +void PluginUpdater::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + emit downloadProgress(bytesReceived, bytesTotal); +} + +void PluginUpdater::handleDownloadFinished() +{ + auto reply = qobject_cast(sender()); + QTC_ASSERT(reply, return); + + if (reply->error() != QNetworkReply::NoError) { + emit downloadError(reply->errorString()); + reply->deleteLater(); + return; + } + + QString downloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + + QDir::separator() + "qodeassisttemp"; + QDir().mkpath(downloadPath); + + QString filePath = downloadPath + "/" + m_lastUpdateInfo.fileName; + QFile file(filePath); + + if (!file.open(QIODevice::WriteOnly)) { + emit downloadError(tr("Could not save the update file")); + reply->deleteLater(); + return; + } + + file.write(reply->readAll()); + file.close(); + + if (!Core::executePluginInstallWizard(Utils::FilePath::fromString(filePath))) { + emit downloadError(tr("Failed to install the update")); + } else { + emit downloadFinished(filePath); + } + + auto tempDir = QDir(downloadPath); + if (tempDir.exists()) { + tempDir.removeRecursively(); + } + + reply->deleteLater(); +} + +} // namespace QodeAssist diff --git a/settings/PluginUpdater.hpp b/settings/PluginUpdater.hpp new file mode 100644 index 0000000..13dc83b --- /dev/null +++ b/settings/PluginUpdater.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace QodeAssist { + +class PluginUpdater : public QObject +{ + Q_OBJECT +public: + struct UpdateInfo + { + QString version; + QString downloadUrl; + QString changeLog; + QString fileName; + bool isUpdateAvailable; + bool incompatibleIdeVersion{false}; + QString targetIdeVersion; + QString currentIdeVersion; + }; + + explicit PluginUpdater(QObject *parent = nullptr); + ~PluginUpdater() = default; + + void checkForUpdates(); + void downloadUpdate(const QString &url); + QString currentVersion() const; + bool isUpdateAvailable() const; + +signals: + void updateCheckFinished(const UpdateInfo &info); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadFinished(const QString &filePath); + void downloadError(const QString &error); + +private: + void handleUpdateResponse(QNetworkReply *reply); + void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void handleDownloadFinished(); + QString getUpdateUrl() const; + + QNetworkAccessManager *m_networkManager; + UpdateInfo m_lastUpdateInfo; + bool m_isCheckingUpdate{false}; +}; + +} // namespace QodeAssist diff --git a/settings/SettingsConstants.hpp b/settings/SettingsConstants.hpp index 5a6e051..faa921c 100644 --- a/settings/SettingsConstants.hpp +++ b/settings/SettingsConstants.hpp @@ -50,6 +50,8 @@ const char CA_URL_HISTORY[] = "QodeAssist.caUrlHistory"; const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist"; const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion"; const char ENABLE_LOGGING[] = "QodeAssist.enableLogging"; +const char ENABLE_CHECK_UPDATE[] = "QodeAssist.enableCheckUpdate"; + const char PROVIDER_PATHS[] = "QodeAssist.providerPaths"; const char СС_START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer"; const char СС_AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCharThreshold"; diff --git a/settings/SettingsTr.hpp b/settings/SettingsTr.hpp index 19e0a0e..ca8aac3 100644 --- a/settings/SettingsTr.hpp +++ b/settings/SettingsTr.hpp @@ -28,6 +28,7 @@ inline const char *ENABLE_QODE_ASSIST = QT_TRANSLATE_NOOP("QtC::QodeAssist", "En inline const char *GENERAL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "General"); inline const char *RESET_TO_DEFAULTS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Reset Page to Defaults"); +inline const char *CHECK_UPDATE = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Check Update"); inline const char *SELECT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select..."); inline const char *PROVIDER = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Provider:"); inline const char *MODEL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Model:"); @@ -36,6 +37,9 @@ inline const char *URL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "URL:"); inline const char *STATUS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Status:"); inline const char *TEST = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Test"); inline const char *ENABLE_LOG = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enable Logging"); +inline const char *ENABLE_CHECK_UPDATE_ON_START + = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Check for updates when Qt Creator starts"); + inline const char *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion"); inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant"); inline const char *RESET_SETTINGS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Reset Settings"); diff --git a/settings/UpdateDialog.cpp b/settings/UpdateDialog.cpp new file mode 100644 index 0000000..63d7ced --- /dev/null +++ b/settings/UpdateDialog.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2024 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#include "UpdateDialog.hpp" + +namespace QodeAssist { + +UpdateDialog::UpdateDialog(QWidget *parent) + : QDialog(parent) + , m_updater(new PluginUpdater(this)) +{ + setWindowTitle(tr("QodeAssist Update")); + setMinimumWidth(400); + setMinimumHeight(500); + + m_layout = new QVBoxLayout(this); + m_layout->setSpacing(12); + + auto *supportLabel = new QLabel( + tr("QodeAssist is an open-source project that helps\n" + "developers write better code. If you find it useful, please"), + this); + supportLabel->setAlignment(Qt::AlignCenter); + m_layout->addWidget(supportLabel); + + auto *supportLink = new QLabel( + tr("Support on Ko-fi " + "☕"), + this); + supportLink->setOpenExternalLinks(true); + supportLink->setTextFormat(Qt::RichText); + supportLink->setAlignment(Qt::AlignCenter); + m_layout->addWidget(supportLink); + + m_layout->addSpacing(20); + + m_titleLabel = new QLabel(tr("A new version of QodeAssist is available!"), this); + m_titleLabel->setStyleSheet("font-weight: bold; font-size: 14px;"); + m_titleLabel->setAlignment(Qt::AlignCenter); + m_layout->addWidget(m_titleLabel); + + m_versionLabel = new QLabel( + tr("Version %1 is now available - you have %2").arg(m_updater->currentVersion()), this); + m_versionLabel->setAlignment(Qt::AlignCenter); + m_layout->addWidget(m_versionLabel); + + if (!m_changelogLabel) { + m_changelogLabel = new QLabel(tr("Release Notes:"), this); + m_layout->addWidget(m_changelogLabel); + + m_changelogText = new QTextEdit(this); + m_changelogText->setReadOnly(true); + m_changelogText->setMinimumHeight(100); + m_layout->addWidget(m_changelogText); + } + + m_progress = new QProgressBar(this); + m_progress->setVisible(false); + m_layout->addWidget(m_progress); + + auto *buttonLayout = new QHBoxLayout; + m_downloadButton = new QPushButton(tr("Download and Install"), this); + m_downloadButton->setEnabled(false); + buttonLayout->addWidget(m_downloadButton); + + m_closeButton = new QPushButton(tr("Close"), this); + buttonLayout->addWidget(m_closeButton); + + m_layout->addLayout(buttonLayout); + + connect(m_updater, &PluginUpdater::updateCheckFinished, this, &UpdateDialog::handleUpdateInfo); + connect(m_updater, &PluginUpdater::downloadProgress, this, &UpdateDialog::updateProgress); + connect(m_updater, &PluginUpdater::downloadFinished, this, &UpdateDialog::handleDownloadFinished); + connect(m_updater, &PluginUpdater::downloadError, this, &UpdateDialog::handleDownloadError); + + connect(m_downloadButton, &QPushButton::clicked, this, &UpdateDialog::startDownload); + connect(m_closeButton, &QPushButton::clicked, this, &QDialog::reject); + + m_updater->checkForUpdates(); +} + +void UpdateDialog::checkForUpdatesAndShow(QWidget *parent) +{ + auto *dialog = new UpdateDialog(parent); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); +} + +void UpdateDialog::handleUpdateInfo(const PluginUpdater::UpdateInfo &info) +{ + if (info.incompatibleIdeVersion) { + m_titleLabel->setText(tr("Incompatible Qt Creator Version")); + m_versionLabel->setText(tr("This update requires Qt Creator %1, current is %2.\n" + "Please upgrade Qt Creator to use this version of QodeAssist.") + .arg(info.targetIdeVersion, info.currentIdeVersion)); + m_downloadButton->setEnabled(false); + return; + } + + if (!info.isUpdateAvailable) { + m_titleLabel->setText(tr("QodeAssist is up to date")); + m_downloadButton->setEnabled(false); + return; + } + + m_titleLabel->setText(tr("A new version of QodeAssist is available!")); + m_versionLabel->setText(tr("Version %1 is now available - you have %2") + .arg(info.version, m_updater->currentVersion())); + + if (!info.changeLog.isEmpty()) { + if (!m_changelogLabel) { + m_changelogLabel = new QLabel(tr("Release Notes:"), this); + m_layout->insertWidget(2, m_changelogLabel); + + m_changelogText = new QTextEdit(this); + m_changelogText->setReadOnly(true); + m_changelogText->setMaximumHeight(200); + m_layout->insertWidget(3, m_changelogText); + } + m_changelogText->setText(info.changeLog); + } + + m_downloadButton->setEnabled(true); + m_updateInfo = info; +} + +void UpdateDialog::startDownload() +{ + m_downloadButton->setEnabled(false); + m_progress->setVisible(true); + m_updater->downloadUpdate(m_updateInfo.downloadUrl); +} + +void UpdateDialog::updateProgress(qint64 received, qint64 total) +{ + m_progress->setMaximum(total); + m_progress->setValue(received); +} + +void UpdateDialog::handleDownloadFinished(const QString &path) +{ + m_progress->setVisible(false); + QMessageBox::information( + this, + tr("Update Successful"), + tr("Update has been downloaded and installed. " + "Please restart Qt Creator to apply changes.")); + accept(); +} + +void UpdateDialog::handleDownloadError(const QString &error) +{ + m_progress->setVisible(false); + m_downloadButton->setEnabled(true); + QMessageBox::critical(this, tr("Update Error"), tr("Failed to update: %1").arg(error)); +} + +} // namespace QodeAssist diff --git a/settings/UpdateDialog.hpp b/settings/UpdateDialog.hpp new file mode 100644 index 0000000..4ddc863 --- /dev/null +++ b/settings/UpdateDialog.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "PluginUpdater.hpp" + +namespace QodeAssist { + +class UpdateDialog : public QDialog +{ + Q_OBJECT +public: + explicit UpdateDialog(QWidget *parent = nullptr); + + static void checkForUpdatesAndShow(QWidget *parent = nullptr); + +private slots: + void handleUpdateInfo(const PluginUpdater::UpdateInfo &info); + void startDownload(); + void updateProgress(qint64 received, qint64 total); + void handleDownloadFinished(const QString &path); + void handleDownloadError(const QString &error); + +private: + PluginUpdater *m_updater; + QVBoxLayout *m_layout; + QLabel *m_titleLabel; + QLabel *m_versionLabel; + QLabel *m_changelogLabel{nullptr}; + QTextEdit *m_changelogText{nullptr}; + QProgressBar *m_progress; + QPushButton *m_downloadButton; + QPushButton *m_closeButton; + PluginUpdater::UpdateInfo m_updateInfo; +}; + +} // namespace QodeAssist