diff --git a/dust3d.pro b/dust3d.pro index 5c783fe4..0a12ffa3 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -67,6 +67,18 @@ HOMEPAGE_URL = "https://dust3d.org/" REPOSITORY_URL = "https://github.com/huxingyi/dust3d" ISSUES_URL = "https://github.com/huxingyi/dust3d/issues" REFERENCE_GUIDE_URL = "http://docs.dust3d.org" +UPDATES_CHECKER_URL = "https://dust3d.org/dust3d-updateinfo.xml" + +PLATFORM = "Unknown" +macx { + PLATFORM = "MacOS" +} +win32 { + PLATFORM = "Win32" +} +unix:!macx { + PLATFORM = "Linux" +} QMAKE_TARGET_COMPANY = Dust3D QMAKE_TARGET_PRODUCT = Dust3D @@ -81,6 +93,8 @@ DEFINES += "PROJECT_DEFINED_APP_HOMEPAGE_URL=\"\\\"$$HOMEPAGE_URL\\\"\"" DEFINES += "PROJECT_DEFINED_APP_REPOSITORY_URL=\"\\\"$$REPOSITORY_URL\\\"\"" DEFINES += "PROJECT_DEFINED_APP_ISSUES_URL=\"\\\"$$ISSUES_URL\\\"\"" DEFINES += "PROJECT_DEFINED_APP_REFERENCE_GUIDE_URL=\"\\\"$$REFERENCE_GUIDE_URL\\\"\"" +DEFINES += "PROJECT_DEFINED_APP_UPDATES_CHECKER_URL=\"\\\"$$UPDATES_CHECKER_URL\\\"\"" +DEFINES += "PROJECT_DEFINED_APP_PLATFORM=\"\\\"$$PLATFORM\\\"\"" QMAKE_CXXFLAGS_RELEASE -= -O QMAKE_CXXFLAGS_RELEASE -= -O1 @@ -384,6 +398,12 @@ HEADERS += src/scriptrunner.h SOURCES += src/variablesxml.cpp HEADERS += src/variablesxml.h +SOURCES += src/updateschecker.cpp +HEADERS += src/updateschecker.h + +SOURCES += src/updatescheckwidget.cpp +HEADERS += src/updatescheckwidget.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/languages/dust3d_zh_CN.ts b/languages/dust3d_zh_CN.ts index b4ec0722..c1c81761 100644 --- a/languages/dust3d_zh_CN.ts +++ b/languages/dust3d_zh_CN.ts @@ -358,6 +358,10 @@ Tips: Script 脚本 + + Check for Updates... + 检查新版本... + ExportPreviewWidget @@ -1100,4 +1104,34 @@ Tips: 清除切面 + + UpdatesCheckWidget + + Check for Updates + 检查新版本 + + + Checking for updates... + 正在检查新版本... + + + View + 查看 + + + + UpdatesChecker + + Fetch update info failed, please retry later + 获取更新信息失败,请稍后重试 + + + %1 %2 is currently the newest version available + %1 %2 已经是当前最新可用版本 + + + An update is available: %1 %2 + 有新版本可用:%1 %2 + + diff --git a/resources/model-addax.ds3 b/resources/model-addax.ds3 index 71a64b0e..255866b5 100644 --- a/resources/model-addax.ds3 +++ b/resources/model-addax.ds3 @@ -1,231 +1,231 @@ DUST3D 1.0 xml 0000000193 - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 18001011..162cb608 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -41,6 +41,7 @@ #include "cutfacelistwidget.h" #include "scriptwidget.h" #include "variablesxml.h" +#include "updatescheckwidget.h" int DocumentWindow::m_modelRenderWidgetInitialX = 16; int DocumentWindow::m_modelRenderWidgetInitialY = 16; @@ -54,6 +55,7 @@ std::map g_documentWindows; QTextBrowser *g_acknowlegementsWidget = nullptr; AboutWidget *g_aboutWidget = nullptr; QTextBrowser *g_contributorsWidget = nullptr; +UpdatesCheckWidget *g_updatesCheckWidget = nullptr; void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) { @@ -114,6 +116,17 @@ void DocumentWindow::showAbout() g_aboutWidget->raise(); } +void DocumentWindow::checkForUpdates() +{ + if (!g_updatesCheckWidget) { + g_updatesCheckWidget = new UpdatesCheckWidget; + } + g_updatesCheckWidget->check(); + g_updatesCheckWidget->show(); + g_updatesCheckWidget->activateWindow(); + g_updatesCheckWidget->raise(); +} + DocumentWindow::DocumentWindow() : m_document(nullptr), m_firstShow(true), @@ -706,6 +719,10 @@ DocumentWindow::DocumentWindow() : m_viewSourceAction = new QAction(tr("Source Code"), this); connect(m_viewSourceAction, &QAction::triggered, this, &DocumentWindow::viewSource); m_helpMenu->addAction(m_viewSourceAction); + + m_checkForUpdatesAction = new QAction(tr("Check for Updates..."), this); + connect(m_checkForUpdatesAction, &QAction::triggered, this, &DocumentWindow::checkForUpdates); + m_helpMenu->addAction(m_checkForUpdatesAction); m_helpMenu->addSeparator(); diff --git a/src/documentwindow.h b/src/documentwindow.h index e1722285..5cf6c9e6 100644 --- a/src/documentwindow.h +++ b/src/documentwindow.h @@ -57,6 +57,7 @@ public slots: void gotoHomepage(); void viewSource(); void about(); + void checkForUpdates(); void reportIssues(); void seeAcknowlegements(); void seeContributors(); @@ -167,6 +168,7 @@ private: QAction *m_gotoHomepageAction; QAction *m_viewSourceAction; QAction *m_aboutAction; + QAction *m_checkForUpdatesAction; QAction *m_reportIssuesAction; QAction *m_seeContributorsAction; QAction *m_seeAcknowlegementsAction; diff --git a/src/updateschecker.cpp b/src/updateschecker.cpp new file mode 100644 index 00000000..5e9edf55 --- /dev/null +++ b/src/updateschecker.cpp @@ -0,0 +1,148 @@ +#include +#include "updateschecker.h" +#include "version.h" + +UpdatesChecker::UpdatesChecker() +{ + connect(&m_networkAccessManager, &QNetworkAccessManager::finished, this, &UpdatesChecker::downloadFinished); +} + +void UpdatesChecker::start() +{ + QUrl url(APP_UPDATES_CHECKER_URL); + QNetworkRequest request(url); + m_networkAccessManager.get(request); +} + +const QString &UpdatesChecker::message() const +{ + return m_message; +} + +bool UpdatesChecker::isLatest() const +{ + return m_isLatest; +} + +bool UpdatesChecker::hasError() const +{ + return m_hasError; +} + +bool UpdatesChecker::parseUpdateInfoXml(const QByteArray &updateInfoXml, std::vector *updateItems) +{ + std::vector elementNameStack; + QXmlStreamReader reader(updateInfoXml); + while (!reader.atEnd()) { + reader.readNext(); + if (!reader.isStartElement() && !reader.isEndElement()) { + if (!reader.name().toString().isEmpty()) + qDebug() << "Skip xml element:" << reader.name().toString() << " tokenType:" << reader.tokenType(); + continue; + } + QString baseName = reader.name().toString(); + if (reader.isStartElement()) { + elementNameStack.push_back(baseName); + if (elementNameStack.size() > 10) { + qDebug() << "Invalid xml, element name stack exceed limits"; + return false; + } + } + QStringList nameItems; + for (const auto &nameItem: elementNameStack) { + nameItems.append(nameItem); + } + QString fullName = nameItems.join("."); + if (reader.isEndElement()) + elementNameStack.pop_back(); + if (reader.isStartElement()) { + if (fullName == "updates.update") { + UpdateItem updateItem; + updateItem.forTags = reader.attributes().value("for").toString().toLower(); + updateItem.version = reader.attributes().value("version").toString(); + updateItem.humanVersion = reader.attributes().value("humanVersion").toString(); + updateItem.descriptionUrl = reader.attributes().value("descriptionUrl").toString(); + if (!updateItem.forTags.isEmpty() && + !updateItem.version.isEmpty() && + !updateItem.humanVersion.isEmpty() && + !updateItem.descriptionUrl.isEmpty()) { + if (updateItems->size() >= 100) + return false; + updateItem.forTags = "," + updateItem.forTags + ","; + updateItems->push_back(updateItem); + } + } + } + } + return true; +} + +bool UpdatesChecker::isVersionLessThan(const QString &version, const QString &compareWith) +{ + auto versionTokens = version.split("."); + auto compareWithTokens = compareWith.split("."); + if (versionTokens.size() > 4) { + return false; + } + while (compareWithTokens.size() < 4) + compareWithTokens.push_back("0"); + if (compareWithTokens.size() > 4) { + return false; + } + while (compareWithTokens.size() < 4) + compareWithTokens.push_back("0"); + for (size_t i = 0; i < 4; ++i) { + int left = versionTokens[i].toInt(); + int right = compareWithTokens[i].toInt(); + if (left > right) + return false; + else if (left < right) + return true; + } + return false; +} + +const UpdatesChecker::UpdateItem &UpdatesChecker::matchedUpdateItem() const +{ + return m_matchedUpdateItem; +} + +void UpdatesChecker::downloadFinished(QNetworkReply *reply) +{ + if (reply->error()) { + qDebug() << "Download update info failed:" << qPrintable(reply->errorString()); + m_message = tr("Fetch update info failed, please retry later"); + } else { + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (200 != statusCode) { + qDebug() << "Download update info failed, statusCode:" << statusCode; + m_message = tr("Fetch update info failed, please retry later"); + } else { + std::vector updateItems; + if (!parseUpdateInfoXml(reply->readAll(), &updateItems)) { + m_message = tr("Fetch update info failed, please retry later"); + } else { + m_isLatest = true; + m_hasError = false; + m_message = tr("%1 %2 is currently the newest version available").arg(APP_NAME).arg(APP_HUMAN_VER); + QString platform = QString(APP_PLATFORM).toLower(); + if (!platform.isEmpty()) { + platform = "," + platform + ","; + for (const auto &it: updateItems) { + if (-1 == it.forTags.indexOf(platform)) + continue; + if (isVersionLessThan(APP_VER, it.version)) { + m_isLatest = false; + m_message = tr("An update is available: %1 %2").arg(APP_NAME).arg(it.humanVersion); + m_matchedUpdateItem = it; + break; + } + } + } + } + } + } + reply->deleteLater(); + + emit finished(); +} diff --git a/src/updateschecker.h b/src/updateschecker.h new file mode 100644 index 00000000..2b4070ca --- /dev/null +++ b/src/updateschecker.h @@ -0,0 +1,40 @@ +#ifndef DUST3D_UPDATES_CHECKER_H +#define DUST3D_UPDATES_CHECKER_H +#include +#include + +class UpdatesChecker : public QObject +{ + Q_OBJECT +signals: + void finished(); +public: + struct UpdateItem + { + QString forTags; + QString version; + QString humanVersion; + QString descriptionUrl; + }; + + UpdatesChecker(); + void start(); + bool isLatest() const; + bool hasError() const; + const QString &message() const; + const UpdateItem &matchedUpdateItem() const; +private slots: + void downloadFinished(QNetworkReply *reply); +private: + QNetworkAccessManager m_networkAccessManager; + bool m_isLatest = false; + QString m_message; + QString m_latestUrl; + bool m_hasError = true; + UpdateItem m_matchedUpdateItem; + + bool parseUpdateInfoXml(const QByteArray &updateInfoXml, std::vector *updateItems); + static bool isVersionLessThan(const QString &version, const QString &compareWith); +}; + +#endif diff --git a/src/updatescheckwidget.cpp b/src/updatescheckwidget.cpp new file mode 100644 index 00000000..a4c210d1 --- /dev/null +++ b/src/updatescheckwidget.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include "updatescheckwidget.h" +#include "util.h" +#include "updateschecker.h" + +#define CHECKING_WIDGET_INDEX 0 +#define SHOWING_RESULT_WIDGET_INDEX 1 + +UpdatesCheckWidget::UpdatesCheckWidget() +{ + setWindowTitle(unifiedWindowTitle(tr("Check for Updates"))); + + m_stackedWidget = new QStackedWidget; + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(m_stackedWidget); + + QWidget *checkingWidget = new QWidget; + QWidget *showingResultWidget = new QWidget; + + ////////// checking /////////////////// + + QLabel *busyLabel = new QLabel; + busyLabel->setText(tr("Checking for updates...")); + + QProgressBar *busyBar = new QProgressBar; + busyBar->setMaximum(0); + busyBar->setMinimum(0); + busyBar->setValue(0); + + QVBoxLayout *checkingLayout = new QVBoxLayout; + checkingLayout->addWidget(busyLabel); + checkingLayout->addWidget(busyBar); + + checkingWidget->setLayout(checkingLayout); + + ////////// showing result ///////////// + + m_infoLabel = new QLabel; + + m_viewButton = new QPushButton(tr("View")); + m_viewButton->hide(); + + connect(m_viewButton, &QPushButton::clicked, this, &UpdatesCheckWidget::viewUpdates); + + QVBoxLayout *showingResultLayout = new QVBoxLayout; + showingResultLayout->addWidget(m_infoLabel); + showingResultLayout->addStretch(); + showingResultLayout->addWidget(m_viewButton); + + showingResultWidget->setLayout(showingResultLayout); + + m_stackedWidget->addWidget(checkingWidget); + m_stackedWidget->addWidget(showingResultWidget); + + m_stackedWidget->setCurrentIndex(CHECKING_WIDGET_INDEX); + + setLayout(mainLayout); +} + +UpdatesCheckWidget::~UpdatesCheckWidget() +{ + delete m_updatesChecker; +} + +void UpdatesCheckWidget::viewUpdates() +{ + if (m_viewUrl.isEmpty()) + return; + + QDesktopServices::openUrl(QUrl(m_viewUrl)); +} + +void UpdatesCheckWidget::check() +{ + if (nullptr != m_updatesChecker) + return; + + m_stackedWidget->setCurrentIndex(CHECKING_WIDGET_INDEX); + + m_viewUrl.clear(); + + m_updatesChecker = new UpdatesChecker; + connect(m_updatesChecker, &UpdatesChecker::finished, this, &UpdatesCheckWidget::checkFinished); + m_updatesChecker->start(); +} + +void UpdatesCheckWidget::checkFinished() +{ + m_infoLabel->setText(m_updatesChecker->message()); + if (m_updatesChecker->hasError()) { + m_viewButton->hide(); + } else { + if (m_updatesChecker->isLatest()) { + m_viewButton->hide(); + } else { + m_viewUrl = m_updatesChecker->matchedUpdateItem().descriptionUrl; + m_viewButton->show(); + } + } + m_stackedWidget->setCurrentIndex(SHOWING_RESULT_WIDGET_INDEX); + + m_updatesChecker->deleteLater(); + m_updatesChecker = nullptr; +} diff --git a/src/updatescheckwidget.h b/src/updatescheckwidget.h new file mode 100644 index 00000000..8b7b9a8e --- /dev/null +++ b/src/updatescheckwidget.h @@ -0,0 +1,28 @@ +#ifndef DUST3D_UPDATES_CHECK_WIDGET_H +#define DUST3D_UPDATES_CHECK_WIDGET_H +#include +#include +#include +#include + +class UpdatesChecker; + +class UpdatesCheckWidget : public QDialog +{ + Q_OBJECT +public: + UpdatesCheckWidget(); + ~UpdatesCheckWidget(); +public slots: + void check(); + void checkFinished(); + void viewUpdates(); +private: + UpdatesChecker *m_updatesChecker = nullptr; + QStackedWidget *m_stackedWidget = nullptr; + QLabel *m_infoLabel = nullptr; + QPushButton *m_viewButton = nullptr; + QString m_viewUrl; +}; + +#endif diff --git a/src/version.h b/src/version.h index e0c30f3b..15c2746c 100644 --- a/src/version.h +++ b/src/version.h @@ -9,5 +9,7 @@ #define APP_REPOSITORY_URL PROJECT_DEFINED_APP_REPOSITORY_URL #define APP_ISSUES_URL PROJECT_DEFINED_APP_ISSUES_URL #define APP_REFERENCE_GUIDE_URL PROJECT_DEFINED_APP_REFERENCE_GUIDE_URL +#define APP_UPDATES_CHECKER_URL PROJECT_DEFINED_APP_UPDATES_CHECKER_URL +#define APP_PLATFORM PROJECT_DEFINED_APP_PLATFORM #endif