From 5e9d6d67e9e43ab5b128a0eb5516e5a641b7ce2f Mon Sep 17 00:00:00 2001 From: Colin Duquesnoy Date: Tue, 18 Jul 2017 21:48:13 +0200 Subject: [PATCH] #48 Check for update mechanism Link UpdateToolBar with UpdaterViewModel and add a check for update button. --- .../Updater/Github/GithubReleaseQuerier.cpp | 32 +++++-- .../Updater/Github/GithubReleaseQuerier.hpp | 6 +- .../Github/GithubReleasesReplyParser.cpp | 3 +- .../Application/Updater/Release.cpp | 4 + .../Application/Updater/Release.hpp | 1 + .../Application/Updater/Updater.cpp | 15 +++- .../Application/Updater/Updater.hpp | 20 ++++- .../ViewModels/UpdaterViewModel.cpp | 46 ++++++++++ .../ViewModels/UpdaterViewModel.hpp | 15 +++- .../MellowPlayer/Controls/MainToolBar.qml | 13 +++ .../MellowPlayer/Controls/UpdateToolBar.qml | 87 +++++++++++-------- .../Application/Updater/UpdaterTests.cpp | 8 +- 12 files changed, 191 insertions(+), 59 deletions(-) diff --git a/lib/MellowPlayer/Application/Updater/Github/GithubReleaseQuerier.cpp b/lib/MellowPlayer/Application/Updater/Github/GithubReleaseQuerier.cpp index f6e42d47..9f106d1a 100644 --- a/lib/MellowPlayer/Application/Updater/Github/GithubReleaseQuerier.cpp +++ b/lib/MellowPlayer/Application/Updater/Github/GithubReleaseQuerier.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "GithubReleaseQuerier.hpp" @@ -8,32 +9,47 @@ using namespace MellowPlayer::Application; GithubReleaseQuerier::GithubReleaseQuerier(IHttpClient& httpClient): - httpClient_(httpClient) { - connect(&httpClient, &IHttpClient::replyReceived, - this, &GithubReleaseQuerier::onReplyReceived); + logger_(LoggingManager::instance().getLogger("GithubReleaseQuerier")), httpClient_(httpClient) +{ + connect(&httpClient, &IHttpClient::replyReceived, this, &GithubReleaseQuerier::onReplyReceived); connect(&replyParser_, &GithubReleasesReplyParser::ready, this, &GithubReleaseQuerier::onReleaseReady); } -void GithubReleaseQuerier::setChannel(UpdateChannel channel) { +void GithubReleaseQuerier::setChannel(UpdateChannel channel) +{ channel_ = channel; } -void GithubReleaseQuerier::getLatest() { +void GithubReleaseQuerier::getLatest() +{ + LOG_DEBUG(logger_, "getting releases"); + found_ = false; httpClient_.get("https://api.github.com/repos/ColinDuquesnoy/MellowPlayer/releases"); } -void GithubReleaseQuerier::onReplyReceived(const QByteArray& replyData) { +void GithubReleaseQuerier::onReplyReceived(const QByteArray& replyData) +{ + LOG_DEBUG(logger_, "Reply recevied"); + LOG_TRACE(logger_, "reply data: " << replyData.toStdString()); replyParser_.parse(replyData); + if (!found_) { + LOG_DEBUG(logger_, "no release found"); + emit latestReceived(nullptr); + } } -void GithubReleaseQuerier::onReleaseReady(const Release* release) { +void GithubReleaseQuerier::onReleaseReady(const Release* release) +{ if (release != nullptr && accept(release)) { + LOG_DEBUG(logger_, "Latest release found: " << release->getName()); emit latestReceived(release); replyParser_.stop(); + found_ = true; } } -bool GithubReleaseQuerier::accept(const Release* release) { +bool GithubReleaseQuerier::accept(const Release* release) +{ bool accepted = false; switch (channel_) { diff --git a/lib/MellowPlayer/Application/Updater/Github/GithubReleaseQuerier.hpp b/lib/MellowPlayer/Application/Updater/Github/GithubReleaseQuerier.hpp index bd0ef43f..0cd4417b 100644 --- a/lib/MellowPlayer/Application/Updater/Github/GithubReleaseQuerier.hpp +++ b/lib/MellowPlayer/Application/Updater/Github/GithubReleaseQuerier.hpp @@ -8,6 +8,8 @@ namespace MellowPlayer::Application { + class ILogger; + class GithubReleaseQuerier: public IReleaseQuerier { public: GithubReleaseQuerier(IHttpClient& httpClient); @@ -21,8 +23,10 @@ namespace MellowPlayer::Application { bool accept(const Application::Release* release); private: + ILogger& logger_; GithubReleasesReplyParser replyParser_; IHttpClient& httpClient_; Application::UpdateChannel channel_ = Application::UpdateChannel::Stable; + bool found_ = false; }; -} \ No newline at end of file +} diff --git a/lib/MellowPlayer/Application/Updater/Github/GithubReleasesReplyParser.cpp b/lib/MellowPlayer/Application/Updater/Github/GithubReleasesReplyParser.cpp index 87a78eaa..2e3f3dea 100644 --- a/lib/MellowPlayer/Application/Updater/Github/GithubReleasesReplyParser.cpp +++ b/lib/MellowPlayer/Application/Updater/Github/GithubReleasesReplyParser.cpp @@ -9,7 +9,8 @@ using namespace MellowPlayer::Application; void GithubReleasesReplyParser::parse(const QByteArray& replyData) { stopRequested_ = false; QJsonDocument jsonDocument = QJsonDocument::fromJson(replyData); - assert(jsonDocument.isArray()); + if (!jsonDocument.isArray()) + return; QJsonArray array = jsonDocument.array(); for(int releaseIndex=0; releaseIndex < array.count(); ++releaseIndex) { QJsonObject obj = array.at(releaseIndex).toObject(); diff --git a/lib/MellowPlayer/Application/Updater/Release.cpp b/lib/MellowPlayer/Application/Updater/Release.cpp index a8dd0573..5dc914ef 100644 --- a/lib/MellowPlayer/Application/Updater/Release.cpp +++ b/lib/MellowPlayer/Application/Updater/Release.cpp @@ -28,6 +28,10 @@ Release::Release(const QString& url, const QString& version, const QDate& date, assets_(assets) { } +QString Release::getUrl() const { + return url_; +} + QString Release::getName() const { return name_; } diff --git a/lib/MellowPlayer/Application/Updater/Release.hpp b/lib/MellowPlayer/Application/Updater/Release.hpp index f5e66e50..1b930ea9 100644 --- a/lib/MellowPlayer/Application/Updater/Release.hpp +++ b/lib/MellowPlayer/Application/Updater/Release.hpp @@ -19,6 +19,7 @@ namespace MellowPlayer::Application { Release(const QString& url, const QString& version, const QDate& date, const QString& description, const AssetList& assets, bool preRelease=false, QObject* parent=nullptr); + QString getUrl() const; QString getName() const; QString getDate() const; const QString& getDescription() const; diff --git a/lib/MellowPlayer/Application/Updater/Updater.cpp b/lib/MellowPlayer/Application/Updater/Updater.cpp index 1d101392..7d8ef33a 100644 --- a/lib/MellowPlayer/Application/Updater/Updater.cpp +++ b/lib/MellowPlayer/Application/Updater/Updater.cpp @@ -12,9 +12,10 @@ Updater::Updater(IReleaseQuerier& releaseQuerier, Settings& settings): releaseQuerier_(releaseQuerier), autoCheckEnabledSetting_(settings.get(SettingKey::MAIN_CHECK_FOR_UPDATES)), updateChannelSetting_(settings.get(SettingKey::MAIN_UPDATE_CHANNEL)), - latestRelease_(&Release::current()) { + currentRelease_(&Release::current()) { releaseQuerier.setChannel(getChannel()); connect(&releaseQuerier, &IReleaseQuerier::latestReceived, this, &Updater::onLatestReleaseReceived); + connect(&updateChannelSetting_, &Setting::valueChanged, this, &Updater::check); // Release* r = new Release("1.95.0", QDate::fromString("2017-06-15"), this); // setCurrentRelease(r); @@ -57,16 +58,22 @@ const Release* Updater::getLatestRelease() const { void Updater::onLatestReleaseReceived(const Release* release) { LOG_DEBUG(logger_, "Latest release received: " + release->getName()); - if (*release > *latestRelease_) { + if (release != nullptr && *release > *currentRelease_) { LOG_DEBUG(logger_, QString("Latest release is an update (%1 < %2)").arg( - latestRelease_->getName()).arg(release->getName())); + currentRelease_->getName()).arg(release->getName())); latestRelease_ = release; isUpdateAvailable_ = true; emit updateAvailable(); } + else { + LOG_DEBUG(logger_, QString("No release found")); + latestRelease_ = nullptr; + isUpdateAvailable_ = false; + emit noUpdateAvailable(); + } } void Updater::setCurrentRelease(const Release* currentRelease) { - latestRelease_ = currentRelease; + currentRelease_ = currentRelease; } diff --git a/lib/MellowPlayer/Application/Updater/Updater.hpp b/lib/MellowPlayer/Application/Updater/Updater.hpp index 69a15779..4294a73a 100644 --- a/lib/MellowPlayer/Application/Updater/Updater.hpp +++ b/lib/MellowPlayer/Application/Updater/Updater.hpp @@ -14,21 +14,31 @@ namespace MellowPlayer::Application { class Updater: public QObject { Q_OBJECT +// Q_ENUMS() public: Updater(IReleaseQuerier& releaseQuerier, Settings& settings); - void setCurrentRelease(const Release* currentRelease); +// enum class Status { +// None, +// Checking, +// Downloading, +// Installing +// }; - void check(); - void download(); - void install(); + void setCurrentRelease(const Release* currentRelease); bool isUpdateAvailable() const; bool canInstall() const; const Release* getLatestRelease() const; + public slots: + void check(); + void download(); + void install(); + signals: void updateAvailable(); + void noUpdateAvailable(); private slots: void onLatestReleaseReceived(const Release* release); @@ -41,9 +51,11 @@ namespace MellowPlayer::Application { Setting& updateChannelSetting_; bool isUpdateAvailable_ = false; bool isDownloading_ = false; + const Release* currentRelease_; const Release* latestRelease_ = nullptr; MellowPlayer::Application::UpdateChannel getChannel() const; +// Status status_ = Status::None; }; } diff --git a/lib/MellowPlayer/Presentation/ViewModels/UpdaterViewModel.cpp b/lib/MellowPlayer/Presentation/ViewModels/UpdaterViewModel.cpp index 43f90692..98fd7165 100644 --- a/lib/MellowPlayer/Presentation/ViewModels/UpdaterViewModel.cpp +++ b/lib/MellowPlayer/Presentation/ViewModels/UpdaterViewModel.cpp @@ -6,6 +6,11 @@ using namespace MellowPlayer::Presentation; UpdaterViewModel::UpdaterViewModel(Updater& updater): updater_(updater) { connect(&updater, &Updater::updateAvailable, this, &UpdaterViewModel::onUpdateAvailable); + connect(&updater, &Updater::noUpdateAvailable, this, &UpdaterViewModel::onNoUpdateAvailable); +} + +QString UpdaterViewModel::getMessage() const { + return message_; } bool UpdaterViewModel::isVisible() const { @@ -20,15 +25,25 @@ int UpdaterViewModel::getProgress() const { return progress_; } +bool UpdaterViewModel::isProgressVisible() const +{ + return progressVisible_; +} + void UpdaterViewModel::close() { setVisible(false); } void UpdaterViewModel::check() { + setProgressVisible(true); + setProgress(-1); updater_.check(); } void UpdaterViewModel::install() { + setMessage("Downloading update..."); + setProgressVisible(true); + setProgress(-1); updater_.install(); } @@ -56,8 +71,39 @@ void UpdaterViewModel::setProgress(int progress) { emit progressChanged(); } +void UpdaterViewModel::setProgressVisible(bool progressVisible) +{ + if (progressVisible_ == progressVisible) + return; + + progressVisible_ = progressVisible; + emit progressVisibleChanged(); +} + void UpdaterViewModel::onUpdateAvailable() { setCanInstall(updater_.canInstall()); + setProgressVisible(false); setProgress(-1); setVisible(true); + setMessage(tr("A new version of MellowPlayer is available (%1)").arg(updater_.getLatestRelease()->getName())); +} + +void UpdaterViewModel::onNoUpdateAvailable() +{ + setCanInstall(false); + setProgressVisible(false); + setProgress(-1); + setVisible(false); + setMessage(""); +} + +void UpdaterViewModel::setMessage(const QString& message) { + if (message_ == message) + return; + message_ = message; + emit messageChanged(); +} + +QString UpdaterViewModel::getUrl() const { + return updater_.getLatestRelease()->getUrl(); } diff --git a/lib/MellowPlayer/Presentation/ViewModels/UpdaterViewModel.hpp b/lib/MellowPlayer/Presentation/ViewModels/UpdaterViewModel.hpp index 06eabe57..2dc98e25 100644 --- a/lib/MellowPlayer/Presentation/ViewModels/UpdaterViewModel.hpp +++ b/lib/MellowPlayer/Presentation/ViewModels/UpdaterViewModel.hpp @@ -7,35 +7,48 @@ namespace MellowPlayer::Presentation { class UpdaterViewModel: public QObject { Q_OBJECT + Q_PROPERTY(QString message READ getMessage NOTIFY messageChanged) + Q_PROPERTY(QString url READ getUrl CONSTANT) Q_PROPERTY(bool visible READ isVisible NOTIFY visibleChanged) Q_PROPERTY(bool canInstall READ canInstall NOTIFY canInstallChanged) Q_PROPERTY(int progress READ getProgress NOTIFY progressChanged) + Q_PROPERTY(bool progressVisible READ isProgressVisible NOTIFY progressVisibleChanged) public: UpdaterViewModel(Application::Updater& updater); + QString getMessage() const; + QString getUrl() const; bool isVisible() const; bool canInstall() const; int getProgress() const; + bool isProgressVisible() const; Q_INVOKABLE void close(); Q_INVOKABLE void check(); - void install(); + Q_INVOKABLE void install(); signals: + void messageChanged(); void visibleChanged(); void canInstallChanged(); void progressChanged(); + void progressVisibleChanged(); private slots: + void setMessage(const QString& message); void setVisible(bool visible); void setCanInstall(bool canInstall); void setProgress(int progress); + void setProgressVisible(bool progressVisible); void onUpdateAvailable(); + void onNoUpdateAvailable(); private: Application::Updater& updater_; + QString message_; bool visible_ = false; bool canInstall_ = false; int progress_ = -1; + bool progressVisible_ = true; }; } diff --git a/lib/MellowPlayer/Presentation/Views/MellowPlayer/Controls/MainToolBar.qml b/lib/MellowPlayer/Presentation/Views/MellowPlayer/Controls/MainToolBar.qml index 0884f510..38336b3e 100644 --- a/lib/MellowPlayer/Presentation/Views/MellowPlayer/Controls/MainToolBar.qml +++ b/lib/MellowPlayer/Presentation/Views/MellowPlayer/Controls/MainToolBar.qml @@ -362,6 +362,19 @@ ToolBar { MenuSeparator { } + MenuIconItem { + icon: MaterialIcons.icon_update + text: "Check for updates" + onClicked: _updater.check() + + ProgressBar { + anchors{ bottom: parent.bottom; horizontalCenter: parent.left; right: parent.right } + indeterminate: _updater.progress == -1 + visible: _updater.progressVisible + } + } + + MenuIconItem { id: menuItemAbout diff --git a/lib/MellowPlayer/Presentation/Views/MellowPlayer/Controls/UpdateToolBar.qml b/lib/MellowPlayer/Presentation/Views/MellowPlayer/Controls/UpdateToolBar.qml index 6a88cad4..a56e92a7 100644 --- a/lib/MellowPlayer/Presentation/Views/MellowPlayer/Controls/UpdateToolBar.qml +++ b/lib/MellowPlayer/Presentation/Views/MellowPlayer/Controls/UpdateToolBar.qml @@ -18,55 +18,70 @@ ToolBar { yScale: 0 } - Material.background: _theme.accent - Material.foreground: _theme.isDark(_theme.accent) ? "white" : "#303030" - Material.theme: _theme.isDark(_theme.accent) ? Material.Dark : Material.Light + Material.background: _theme.secondary + Material.foreground: _theme.secondaryForeground + Material.accent: _theme.accent + Material.theme: _theme.isDark(_theme.secondary) ? Material.Dark : Material.Light - RowLayout { + ColumnLayout { anchors.fill: parent - spacing: 8 + spacing: 0 - Item { - width: 4 - } + RowLayout { + spacing: 8 - Label { - text: MaterialIcons.icon_update - font { - family: MaterialIcons.family - pixelSize: 24 + Item { + width: 4 } - } - Label { - text: "A new version of MellowPlayer is available..." - font.pixelSize: 16 - } + Label { + text: MaterialIcons.icon_update + font { + family: MaterialIcons.family + pixelSize: 26 + } + } - Item { - Layout.fillWidth: true - } + Label { + text: _updater.message + font.pixelSize: 16 + } - ToolButton { - hoverEnabled: true - text: "See release notes" - } + Item { + Layout.fillWidth: true + } - ToolButton { - hoverEnabled: true - text: "Install" - } + ToolButton { + highlighted: true + text: "See release notes" + onClicked: Qt.openUrlExternally(_updater.url) + } + + ToolButton { + highlighted: true + text: "Install" + onClicked: _updater.install() +// visible: _updater.canInstall + } - ToolButton { - hoverEnabled: true - text: MaterialIcons.icon_close - font { - family: MaterialIcons.family - pixelSize: 24 + ToolButton { + hoverEnabled: true + text: MaterialIcons.icon_close + font { + family: MaterialIcons.family + pixelSize: 24 + } + onClicked: _updater.close() } - onClicked: _updater.close() + } + + ProgressBar { + anchors{ bottom: parent.bottom; left: parent.left; right: parent.right } + indeterminate: _updater.progress == -1 + visible: _updater.progressVisible } } + states: [ State { when: _updater.visible diff --git a/tests/UnitTests/Application/Updater/UpdaterTests.cpp b/tests/UnitTests/Application/Updater/UpdaterTests.cpp index 32194dbb..1b6ca8cf 100644 --- a/tests/UnitTests/Application/Updater/UpdaterTests.cpp +++ b/tests/UnitTests/Application/Updater/UpdaterTests.cpp @@ -47,7 +47,7 @@ SCENARIO("check for stable updates") { THEN("no update is available") { REQUIRE(!updater.isUpdateAvailable()); - REQUIRE(updater.getLatestRelease()->getName().toStdString() == "2.2.5"); + REQUIRE(updater.getLatestRelease() == nullptr); REQUIRE(updateAvailableSpy.count() == 0); } } @@ -64,7 +64,7 @@ SCENARIO("check for stable updates") { THEN("no update is available") { REQUIRE(!updater.isUpdateAvailable()); - REQUIRE(updater.getLatestRelease()->getName() == "2.95.0"); + REQUIRE(updater.getLatestRelease() == nullptr); REQUIRE(updateAvailableSpy.count() == 0); } } @@ -107,7 +107,7 @@ SCENARIO("check for beta updates") { THEN("no update is available") { REQUIRE(!updater.isUpdateAvailable()); - REQUIRE(updater.getLatestRelease()->getName().toStdString() == "2.95.0"); + REQUIRE(updater.getLatestRelease() == nullptr); REQUIRE(updateAvailableSpy.count() == 0); } } @@ -166,7 +166,7 @@ SCENARIO("check for Continuous updates") { THEN("no update is available") { REQUIRE(!updater.isUpdateAvailable()); - REQUIRE(updater.getLatestRelease()->getName().toStdString() == "2.95.0"); + REQUIRE(updater.getLatestRelease() == nullptr); REQUIRE(updateAvailableSpy.count() == 0); } }