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