From bdb62b405a45c404aca57dd5d1dc8cb5ab551d4f Mon Sep 17 00:00:00 2001 From: casey langen Date: Wed, 28 Jun 2017 00:51:02 -0700 Subject: [PATCH] Added update check functionality. Finally. --- archive-win32.sh | 2 + src/core/support/Common.cpp | 15 ++ src/core/support/Common.h | 1 + src/musikbox/CMakeLists.txt | 1 + src/musikbox/Main.cpp | 1 + src/musikbox/app/layout/MainLayout.cpp | 34 ++++ src/musikbox/app/layout/MainLayout.h | 5 +- src/musikbox/app/util/Messages.h | 2 + src/musikbox/app/util/PreferenceKeys.cpp | 1 + src/musikbox/app/util/PreferenceKeys.h | 1 + src/musikbox/app/util/UpdateCheck.cpp | 192 +++++++++++++++++++++++ src/musikbox/app/util/UpdateCheck.h | 66 ++++++++ src/musikbox/app/util/Version.h | 3 + src/musikbox/data/locales/en_US.json | 8 +- src/musikbox/musikbox.vcxproj | 6 +- src/musikbox/musikbox.vcxproj.filters | 6 + 16 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 src/musikbox/app/util/UpdateCheck.cpp create mode 100644 src/musikbox/app/util/UpdateCheck.h diff --git a/archive-win32.sh b/archive-win32.sh index 2b53935ed..f4e57670d 100644 --- a/archive-win32.sh +++ b/archive-win32.sh @@ -17,6 +17,7 @@ mkdir -p "$VANILLA/plugins" mkdir -p "$VANILLA/themes" mkdir -p "$VANILLA/locales" cp bin/release/musikbox.exe "$VANILLA" +cp bin/release/*.dll "$VANILLA" cp bin/release/plugins/*.dll "$VANILLA/plugins" cp bin/release/themes/*.json "$VANILLA/themes" cp bin/release/locales/*.json "$VANILLA/locales" @@ -30,6 +31,7 @@ mkdir -p "$MILKDROP/plugins" mkdir -p "$MILKDROP/themes" mkdir -p "$MILKDROP/locales" cp bin/release/musikbox.exe "$MILKDROP" +cp bin/release/*.dll "$MILKDROP" cp bin/release/plugins/*.dll "$MILKDROP/plugins" cp bin/release/themes/*.json "$MILKDROP/themes" cp bin/release/locales/*.json "$MILKDROP/locales" diff --git a/src/core/support/Common.cpp b/src/core/support/Common.cpp index 5ce586b91..7fb5d80b1 100644 --- a/src/core/support/Common.cpp +++ b/src/core/support/Common.cpp @@ -137,6 +137,21 @@ std::string musik::core::GetDataDirectory(bool create) { return directory; } +inline void silentDelete(const std::string fn) { + boost::system::error_code ec; + boost::filesystem::remove(boost::filesystem::path(fn), ec); +} + +void musik::core::RemoveOldDlls() { +#ifdef WIN32 + std::string path = GetPluginDirectory(); + silentDelete(path + "libcurl.dll"); + silentDelete(path + "crypto-41.dll"); + silentDelete(path + "ssl-43.dll"); + silentDelete(path + "tls-15.dll"); +#endif +} + void musik::core::MigrateOldDataDirectory() { std::string oldDirectory = diff --git a/src/core/support/Common.h b/src/core/support/Common.h index f6b74353d..a48921c70 100644 --- a/src/core/support/Common.h +++ b/src/core/support/Common.h @@ -50,5 +50,6 @@ namespace musik { namespace core { /* renames ~/.mC2 -> ~/.musikcube */ void MigrateOldDataDirectory(); + void RemoveOldDlls(); } } diff --git a/src/musikbox/CMakeLists.txt b/src/musikbox/CMakeLists.txt index 169d6c194..613c02074 100644 --- a/src/musikbox/CMakeLists.txt +++ b/src/musikbox/CMakeLists.txt @@ -21,6 +21,7 @@ set (BOX_SRCS ./app/util/GlobalHotkeys.cpp ./app/util/PreferenceKeys.cpp ./app/util/Playback.cpp + ./app/util/UpdateCheck.cpp ./app/window/CategoryListView.cpp ./app/window/LogWindow.cpp ./app/window/TrackListView.cpp diff --git a/src/musikbox/Main.cpp b/src/musikbox/Main.cpp index 4184f09e3..1cb80c8fe 100644 --- a/src/musikbox/Main.cpp +++ b/src/musikbox/Main.cpp @@ -106,6 +106,7 @@ int main(int argc, char* argv[]) { musik::core::i18n::Locale::Instance().Initialize(musik::core::GetApplicationDirectory() + "/locales/"); #ifdef WIN32 + musik::core::RemoveOldDlls(); AddDllDirectory(u8to16(musik::core::GetPluginDirectory()).c_str()); #endif diff --git a/src/musikbox/app/layout/MainLayout.cpp b/src/musikbox/app/layout/MainLayout.cpp index e297e3030..661e34b4f 100755 --- a/src/musikbox/app/layout/MainLayout.cpp +++ b/src/musikbox/app/layout/MainLayout.cpp @@ -41,6 +41,7 @@ #include #include +#include #include "SettingsLayout.h" #include "MainLayout.h" @@ -51,6 +52,8 @@ using namespace musik::core; using namespace musik::core::runtime; using namespace cursespp; +static UpdateCheck updateCheck; + static void updateSyncingText(TextLabel* label, int updates) { try { if (updates <= 0) { @@ -82,9 +85,12 @@ MainLayout::MainLayout(ILibraryPtr library) library->Indexer()->Started.connect(this, &MainLayout::OnIndexerStarted); library->Indexer()->Finished.connect(this, &MainLayout::OnIndexerFinished); library->Indexer()->Progress.connect(this, &MainLayout::OnIndexerProgress); + + this->RunUpdateCheck(); } MainLayout::~MainLayout() { + updateCheck.Cancel(); } void MainLayout::ResizeToViewport() { @@ -282,4 +288,32 @@ void MainLayout::OnIndexerProgress(int count) { void MainLayout::OnIndexerFinished(int count) { this->PostMessage(message::IndexerFinished); +} + +void MainLayout::RunUpdateCheck() { + updateCheck.Run([this](bool updateRequired, std::string version, std::string url) { + if (updateRequired) { + std::string prefKey = prefs::keys::LastAcknowledgedUpdateVersion; + std::string acknowledged = this->prefs->GetString(prefKey); + if (acknowledged != version) { + std::shared_ptr dialog(new DialogOverlay()); + + std::string message = boost::str(boost::format( + _TSTR("update_check_dialog_message")) % version % url); + + (*dialog) + .SetTitle(_TSTR("update_check_dialog_title")) + .SetMessage(message) + .AddButton( + "KEY_ENTER", "ENTER", _TSTR("button_dont_remind_me"), + [this, prefKey, version](std::string key) { + this->prefs->SetString(prefKey.c_str(), version.c_str()); + this->prefs->Save(); + }) + .AddButton("^[", "ESC", _TSTR("button_remind_me_later")); + + App::Overlays().Push(dialog); + } + } + }); } \ No newline at end of file diff --git a/src/musikbox/app/layout/MainLayout.h b/src/musikbox/app/layout/MainLayout.h index 31e5bace9..0f6924623 100755 --- a/src/musikbox/app/layout/MainLayout.h +++ b/src/musikbox/app/layout/MainLayout.h @@ -77,9 +77,10 @@ namespace musik { void OnIndexerStarted(); void OnIndexerProgress(int count); void OnIndexerFinished(int count); - - + void Initialize(); + void RunUpdateCheck(); + cursespp::IWindowPtr BlurShortcuts(); void FocusShortcuts(); diff --git a/src/musikbox/app/util/Messages.h b/src/musikbox/app/util/Messages.h index ee631cdac..f5ca4edc7 100644 --- a/src/musikbox/app/util/Messages.h +++ b/src/musikbox/app/util/Messages.h @@ -63,6 +63,8 @@ namespace musik { static const int TracksAddedToPlaylist = 1031; static const int PlaylistCreated = 1032; + + static const int UpdateCheckFinished = 1033; } } } diff --git a/src/musikbox/app/util/PreferenceKeys.cpp b/src/musikbox/app/util/PreferenceKeys.cpp index edb4964b9..9bb2580db 100644 --- a/src/musikbox/app/util/PreferenceKeys.cpp +++ b/src/musikbox/app/util/PreferenceKeys.cpp @@ -43,6 +43,7 @@ namespace musik { namespace box { namespace prefs { const std::string keys::ColorTheme = "ColorTheme"; const std::string keys::MinimizeToTray = "MinimizeToTray"; const std::string keys::StartMinimized = "StartMinimized"; + const std::string keys::LastAcknowledgedUpdateVersion = "LastAcknowledgedUpdateVersion"; } } } diff --git a/src/musikbox/app/util/PreferenceKeys.h b/src/musikbox/app/util/PreferenceKeys.h index 6d0e5b23d..fabbdbced 100644 --- a/src/musikbox/app/util/PreferenceKeys.h +++ b/src/musikbox/app/util/PreferenceKeys.h @@ -45,6 +45,7 @@ namespace musik { namespace box { namespace prefs { extern const std::string ColorTheme; extern const std::string MinimizeToTray; extern const std::string StartMinimized; + extern const std::string LastAcknowledgedUpdateVersion; } } } } diff --git a/src/musikbox/app/util/UpdateCheck.cpp b/src/musikbox/app/util/UpdateCheck.cpp new file mode 100644 index 000000000..ca768a3a5 --- /dev/null +++ b/src/musikbox/app/util/UpdateCheck.cpp @@ -0,0 +1,192 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////////// + +#include "stdafx.h" +#include "UpdateCheck.h" +#include + +#include +#include +#include +#include + +using namespace nlohmann; +using namespace musik::box; +using namespace musik::core::runtime; + +static const std::string UPDATE_CHECK_URL = "https://musikcube.com/version"; +static const std::string LATEST = "latest"; +static const std::string MAJOR = "major"; +static const std::string MINOR = "minor"; +static const std::string PATCH = "patch"; +static const std::string URL = "url"; + +#ifdef WIN32 +static const std::string PLATFORM = "win32"; +#elif defined __APPLE__ +static const std::string PLATFORM = "macos"; +#else +static const std::string PLATFORM = "linux"; +#endif + +static inline int64_t versionCode(short major, short minor, short patch) { + int64_t version = major; + version = (version << 16) | minor; + version = (version << 16) | patch; + return version; +} + +static inline std::string formattedVersion(short major, short minor, short patch) { + return boost::str(boost::format("%d.%d.%d") % major % minor % patch); +} + +size_t UpdateCheck::curlWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata) { + if (ptr && userdata) { + UpdateCheck* context = static_cast(userdata); + + if (context->cancel) { + return 0; /* aborts */ + } + + context->result += std::string(ptr, size * nmemb); + } + return size * nmemb; +} + +UpdateCheck::UpdateCheck() { + this->curl = nullptr; +} + +bool UpdateCheck::Run(Callback callback) { + std::unique_lock lock(this->mutex); + + if (this->thread || !callback) { + return false; + } + + this->Reset(); + this->callback = callback; + + this->curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, UPDATE_CHECK_URL.c_str()); + curl_easy_setopt(curl, CURLOPT_HEADER, 0); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "musikcube UpdateCheck"); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curlWriteCallback); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3000); + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 7500); + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 500); + + this->thread.reset(new std::thread([this] { + bool needsUpdate = false; + + if (curl_easy_perform(this->curl) == CURLE_OK) { + try { + json data = json::parse(this->result); + auto platform = data[LATEST][PLATFORM]; + short major = platform[MAJOR]; + short minor = platform[MINOR]; + short patch = platform[PATCH]; + + this->updateUrl = platform[URL]; + this->latestVersion = formattedVersion(major, minor, patch); + + int64_t current = versionCode(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); + int64_t latest = versionCode(major, minor, patch); + needsUpdate = latest > current; + } + catch (...) { + /* malformed. nothing we can do. */ + } + } + + cursespp::Window::MessageQueue().Post( + Message::Create(this, message::UpdateCheckFinished, needsUpdate)); + })); + + return true; +} + +void UpdateCheck::Cancel() { + std::unique_lock lock(this->mutex); + + if (this->thread) { + this->cancel = true; + if (this->thread->joinable()) { + this->thread->join(); + } + this->Reset(); + } +} + +void UpdateCheck::Reset() { + std::unique_lock lock(this->mutex); + + if (this->curl) { + curl_easy_cleanup(this->curl); + this->curl = nullptr; + } + + this->cancel = false; + this->callback = Callback(); + this->result = ""; + + if (this->thread && this->thread->joinable()) { + this->thread->detach(); + } + + this->thread.reset(); +} + +void UpdateCheck::ProcessMessage(IMessage &message) { + if (message.Type() == message::UpdateCheckFinished) { + auto callback = this->callback; + + this->Reset(); + + if (callback) { + bool updateRequired = message.UserData1() != 0; + callback(updateRequired, this->latestVersion, this->updateUrl); + } + } +} \ No newline at end of file diff --git a/src/musikbox/app/util/UpdateCheck.h b/src/musikbox/app/util/UpdateCheck.h new file mode 100644 index 000000000..383130079 --- /dev/null +++ b/src/musikbox/app/util/UpdateCheck.h @@ -0,0 +1,66 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +namespace musik { namespace box { + class UpdateCheck : private musik::core::runtime::IMessageTarget { + public: + /* args = updateRequired, version, url */ + using Callback = std::function; + + UpdateCheck(); + bool Run(Callback callback); + void Cancel(); + + private: + void Reset(); + + static size_t curlWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata); + + virtual void ProcessMessage(musik::core::runtime::IMessage &message); + + std::recursive_mutex mutex; + std::shared_ptr thread; + + Callback callback; + std::string result, latestVersion, updateUrl; + bool cancel; + CURL* curl; + }; + +} } \ No newline at end of file diff --git a/src/musikbox/app/util/Version.h b/src/musikbox/app/util/Version.h index 110356d70..b4f154cc5 100644 --- a/src/musikbox/app/util/Version.h +++ b/src/musikbox/app/util/Version.h @@ -1,3 +1,6 @@ #pragma once +#define VERSION_MAJOR 0 +#define VERSION_MINOR 18 +#define VERSION_PATCH 0 #define VERSION "0.18.0" diff --git a/src/musikbox/data/locales/en_US.json b/src/musikbox/data/locales/en_US.json index 13488e617..9d2a743a4 100644 --- a/src/musikbox/data/locales/en_US.json +++ b/src/musikbox/data/locales/en_US.json @@ -8,6 +8,9 @@ "button_no": "no", "button_save": "save", "button_cancel": "cancel", + "button_close": "close", + "button_dont_remind_me": "don't remind me again", + "button_remind_me_later": "remind me later", "browse_title_artists": "artists", "browse_title_albums": "albums", @@ -118,7 +121,10 @@ "tracklist_unknown_album": "[unknown album]", "main_syncing_banner_start": "syncing metadata...", - "main_syncing_banner": "syncing metadata (%d tracks processed)" + "main_syncing_banner": "syncing metadata (%d tracks processed)", + + "update_check_dialog_title": "new version available!", + "update_check_dialog_message": "musikbox version '%s' is now available for download. a changelog and binaries are available at:\n\n%s" }, "dimensions": { diff --git a/src/musikbox/musikbox.vcxproj b/src/musikbox/musikbox.vcxproj index b0132029a..57ad5471a 100755 --- a/src/musikbox/musikbox.vcxproj +++ b/src/musikbox/musikbox.vcxproj @@ -89,7 +89,7 @@ xcopy "$(ProjectDir)data\themes\*" "$(TargetDir)themes\" /Y /e if not exist "$(TargetDir)locales" mkdir "$(TargetDir)locales" xcopy "$(ProjectDir)data\locales\*" "$(TargetDir)locales\" /Y /e -xcopy "$(SolutionDir)src\3rdparty\win32_bin\*" "$(TargetDir)plugins" /Y /e +xcopy "$(SolutionDir)src\3rdparty\win32_bin\*" "$(TargetDir)" /Y /e xcopy "$(SolutionDir)src\plugins\websocket_remote\3rdparty\win32_bin\$(Configuration)\*" "$(TargetDir)plugins" /Y /e @@ -130,7 +130,7 @@ xcopy "$(SolutionDir)src\plugins\websocket_remote\3rdparty\win32_bin\$(Configura xcopy "$(ProjectDir)data\themes\*" "$(TargetDir)themes\" /Y /e if not exist "$(TargetDir)locales" mkdir "$(TargetDir)locales" xcopy "$(ProjectDir)data\locales\*" "$(TargetDir)locales\" /Y /e -xcopy "$(SolutionDir)src\3rdparty\win32_bin\*" "$(TargetDir)plugins" /Y /e +xcopy "$(SolutionDir)src\3rdparty\win32_bin\*" "$(TargetDir)" /Y /e xcopy "$(SolutionDir)src\plugins\websocket_remote\3rdparty\win32_bin\$(Configuration)\*" "$(TargetDir)plugins" /Y /e @@ -155,6 +155,7 @@ xcopy "$(SolutionDir)src\plugins\websocket_remote\3rdparty\win32_bin\$(Configura + @@ -209,6 +210,7 @@ xcopy "$(SolutionDir)src\plugins\websocket_remote\3rdparty\win32_bin\$(Configura + diff --git a/src/musikbox/musikbox.vcxproj.filters b/src/musikbox/musikbox.vcxproj.filters index a59864421..13b3b167e 100755 --- a/src/musikbox/musikbox.vcxproj.filters +++ b/src/musikbox/musikbox.vcxproj.filters @@ -138,6 +138,9 @@ app\overlay + + app\util + @@ -325,6 +328,9 @@ app\overlay + + app\util +