From fadd578b60ecf2f62b8b7607ddadf5f9eb3344e8 Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Thu, 16 Jan 2020 16:37:56 +0000 Subject: [PATCH] Projucer: Updated the autoupdater --- extras/Projucer/Builds/LinuxMakefile/Makefile | 6 + .../MacOSX/Projucer.xcodeproj/project.pbxproj | 21 ++ .../VisualStudio2015/Projucer_App.vcxproj | 2 + .../Projucer_App.vcxproj.filters | 6 + .../VisualStudio2017/Projucer_App.vcxproj | 2 + .../Projucer_App.vcxproj.filters | 6 + .../VisualStudio2019/Projucer_App.vcxproj | 2 + .../Projucer_App.vcxproj.filters | 6 + extras/Projucer/Projucer.jucer | 4 + .../Source/Application/jucer_AutoUpdater.cpp | 285 ++++++++---------- .../Source/Application/jucer_AutoUpdater.h | 13 +- .../jucer_DownloadCompileEngineThread.cpp | 80 ++--- .../Utility/Helpers/jucer_VersionInfo.cpp | 115 +++++++ .../Utility/Helpers/jucer_VersionInfo.h | 54 ++++ 14 files changed, 394 insertions(+), 208 deletions(-) create mode 100644 extras/Projucer/Source/Utility/Helpers/jucer_VersionInfo.cpp create mode 100644 extras/Projucer/Source/Utility/Helpers/jucer_VersionInfo.h diff --git a/extras/Projucer/Builds/LinuxMakefile/Makefile b/extras/Projucer/Builds/LinuxMakefile/Makefile index 25b490460d..4aa68333ba 100644 --- a/extras/Projucer/Builds/LinuxMakefile/Makefile +++ b/extras/Projucer/Builds/LinuxMakefile/Makefile @@ -113,6 +113,7 @@ OBJECTS_APP := \ $(JUCE_OBJDIR)/jucer_CodeHelpers_1e797672.o \ $(JUCE_OBJDIR)/jucer_FileHelpers_54f12f83.o \ $(JUCE_OBJDIR)/jucer_MiscUtilities_31fc8dd8.o \ + $(JUCE_OBJDIR)/jucer_VersionInfo_46f3ed40.o \ $(JUCE_OBJDIR)/jucer_PIPGenerator_fd3402c7.o \ $(JUCE_OBJDIR)/jucer_Icons_d02d18f1.o \ $(JUCE_OBJDIR)/jucer_JucerTreeViewBase_9b9f2ff0.o \ @@ -368,6 +369,11 @@ $(JUCE_OBJDIR)/jucer_MiscUtilities_31fc8dd8.o: ../../Source/Utility/Helpers/juce @echo "Compiling jucer_MiscUtilities.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<" +$(JUCE_OBJDIR)/jucer_VersionInfo_46f3ed40.o: ../../Source/Utility/Helpers/jucer_VersionInfo.cpp + -$(V_AT)mkdir -p $(JUCE_OBJDIR) + @echo "Compiling jucer_VersionInfo.cpp" + $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<" + $(JUCE_OBJDIR)/jucer_PIPGenerator_fd3402c7.o: ../../Source/Utility/PIPs/jucer_PIPGenerator.cpp -$(V_AT)mkdir -p $(JUCE_OBJDIR) @echo "Compiling jucer_PIPGenerator.cpp" diff --git a/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj b/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj index 435da116f6..c744f52bf5 100644 --- a/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj +++ b/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj @@ -257,6 +257,10 @@ isa = PBXBuildFile; fileRef = 486E8D02DAD2A0BF54A901C0; }; + 44AD0D81A65C5EAE3BE588FD = { + isa = PBXBuildFile; + fileRef = FF3A6A384D536E1AEF47CD54; + }; 638C7247B6DBA67EFE46E124 = { isa = PBXBuildFile; fileRef = 191330B20DAC08B890656EA0; @@ -2092,6 +2096,13 @@ path = "../../Source/Wizards/jucer_TemplateThumbnailsComponent.h"; sourceTree = "SOURCE_ROOT"; }; + C16F9F479A3A5F6DAD7647A2 = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = "jucer_VersionInfo.h"; + path = "../../Source/Utility/Helpers/jucer_VersionInfo.h"; + sourceTree = "SOURCE_ROOT"; + }; C187718F7B9EBA88584B43F3 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; @@ -2582,6 +2593,13 @@ path = "../../Source/Utility/UI/jucer_ProjucerLookAndFeel.h"; sourceTree = "SOURCE_ROOT"; }; + FF3A6A384D536E1AEF47CD54 = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + name = "jucer_VersionInfo.cpp"; + path = "../../Source/Utility/Helpers/jucer_VersionInfo.cpp"; + sourceTree = "SOURCE_ROOT"; + }; FF68231DE2B395461009116C = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; @@ -3011,6 +3029,8 @@ A6C4AE13FB409DE414094CFA, 6FD8DBC0FF42C87D8BEE2452, 00515BA4EC5A7D4DC078ED37, + FF3A6A384D536E1AEF47CD54, + C16F9F479A3A5F6DAD7647A2, ); name = Helpers; sourceTree = ""; @@ -3455,6 +3475,7 @@ 8BE478303CDF061B72F219E2, BF913199032B4CE970E82AA3, 25EF9B3FECB4C9F0F522DCAA, + 44AD0D81A65C5EAE3BE588FD, 638C7247B6DBA67EFE46E124, D0E26EB54B0087C8BE3D541E, 468548FB21D264DC12321327, diff --git a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj index e768bb253c..d9d33d4e11 100644 --- a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj @@ -233,6 +233,7 @@ + @@ -1624,6 +1625,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters index b0e8a75e26..4499c317a5 100644 --- a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters @@ -496,6 +496,9 @@ Projucer\Utility\Helpers + + Projucer\Utility\Helpers + Projucer\Utility\PIPs @@ -2325,6 +2328,9 @@ Projucer\Utility\Helpers + + Projucer\Utility\Helpers + Projucer\Utility\PIPs diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj index acae1ecf21..a05a2df20b 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj @@ -233,6 +233,7 @@ + @@ -1624,6 +1625,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters index ddb4d98f78..93ec55f171 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters @@ -496,6 +496,9 @@ Projucer\Utility\Helpers + + Projucer\Utility\Helpers + Projucer\Utility\PIPs @@ -2325,6 +2328,9 @@ Projucer\Utility\Helpers + + Projucer\Utility\Helpers + Projucer\Utility\PIPs diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj index bb2ad0023c..b4f6b4dc0f 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj @@ -233,6 +233,7 @@ + @@ -1624,6 +1625,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters index cb3b2b843a..5b56f8c3fb 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters @@ -496,6 +496,9 @@ Projucer\Utility\Helpers + + Projucer\Utility\Helpers + Projucer\Utility\PIPs @@ -2325,6 +2328,9 @@ Projucer\Utility\Helpers + + Projucer\Utility\Helpers + Projucer\Utility\PIPs diff --git a/extras/Projucer/Projucer.jucer b/extras/Projucer/Projucer.jucer index 6f4f708036..3766e94f2e 100644 --- a/extras/Projucer/Projucer.jucer +++ b/extras/Projucer/Projucer.jucer @@ -636,6 +636,10 @@ file="Source/Utility/Helpers/jucer_TranslationHelpers.h"/> + + > 16; - int minor = (versionNum & 0x00ff00) >> 8; - int build = (versionNum & 0x0000ff) >> 0; - - return String (major) + '.' + String (minor) + '.' + String (build); - } + if (showAlertWindows) + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "Update Server Communication Error", + "Failed to communicate with the JUCE update server.\n" + "Please try again in a few minutes.\n\n" + "If this problem persists you can download the latest version of JUCE from juce.com"); - String getProductVersionString() - { - return formatProductVersion (ProjectInfo::versionNumber); + return; } - bool isNewVersion (const String& current, const String& other) + if (! info->isNewerVersionThanCurrent()) { - auto currentTokens = StringArray::fromTokens (current, ".", {}); - auto otherTokens = StringArray::fromTokens (other, ".", {}); - - jassert (currentTokens.size() == 3 && otherTokens.size() == 3); - - if (currentTokens[0].getIntValue() == otherTokens[0].getIntValue()) - { - if (currentTokens[1].getIntValue() == otherTokens[1].getIntValue()) - return currentTokens[2].getIntValue() < otherTokens[2].getIntValue(); - - return currentTokens[1].getIntValue() < otherTokens[1].getIntValue(); - } - - return currentTokens[0].getIntValue() < otherTokens[0].getIntValue(); - } -} - -void LatestVersionCheckerAndUpdater::queryUpdateServer() -{ - StringPairArray responseHeaders; - - URL latestVersionURL ("https://my.roli.com/software_versions/update_to/Projucer/" - + VersionHelpers::getProductVersionString() + '/' + getOSString() - + "?language=" + SystemStats::getUserLanguage()); - - std::unique_ptr inStream (latestVersionURL.createInputStream (false, nullptr, nullptr, - "X-API-Key: 265441b-343403c-20f6932-76361d\nContent-Type: " - "application/json\nAccept: application/json; version=1", - 0, &responseHeaders, &statusCode, 0)); - - if (threadShouldExit()) + if (showAlertWindows) + AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, + "No New Version Available", + "Your JUCE version is up to date."); return; - - if (inStream.get() != nullptr && (statusCode == 303 || statusCode == 400)) - { - if (statusCode == 303) - relativeDownloadPath = responseHeaders["Location"]; - - jassert (relativeDownloadPath.isNotEmpty()); - - jsonReply = JSON::parse (inStream->readEntireStreamAsString()); } - else if (showAlertWindows) + + auto osString = [] { - if (statusCode == 204) - AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, "No New Version Available", "Your JUCE version is up to date."); - else - AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Network Error", "Could not connect to the web server.\n" - "Please check your internet connection and try again."); - } -} + #if JUCE_MAC + return "osx"; + #elif JUCE_WINDOWS + return "windows"; + #elif JUCE_LINUX + return "linux"; + #else + jassertfalse; + return "Unknown"; + #endif + }(); -void LatestVersionCheckerAndUpdater::processResult() -{ - if (! jsonReply.isObject()) - return; + String requiredFilename ("juce-" + info->versionString + "-" + osString + ".zip"); - if (statusCode == 400) + for (auto& asset : info->assets) { - auto errorObject = jsonReply.getDynamicObject()->getProperty ("error"); - - if (errorObject.isObject()) + if (asset.name == requiredFilename) { - auto message = errorObject.getProperty ("message", {}).toString(); + auto versionString = info->versionString; + auto releaseNotes = info->releaseNotes; + + MessageManager::callAsync ([this, versionString, releaseNotes, asset] + { + askUserAboutNewVersion (versionString, releaseNotes, asset); + }); - if (message.isNotEmpty()) - AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "JUCE Updater", message); + return; } } - else if (statusCode == 303) - { - askUserAboutNewVersion (jsonReply.getProperty ("version", {}).toString(), - jsonReply.getProperty ("notes", {}).toString()); - } + + if (showAlertWindows) + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "Failed to find any new downloads", + "Please try again in a few minutes."); } //============================================================================== @@ -246,14 +192,15 @@ public: RectanglePlacement::stretchToFit, 1.0f); } - static std::unique_ptr launchDialog (const String& newVersion, const String& releaseNotes) + static std::unique_ptr launchDialog (const String& newVersionString, + const String& releaseNotes) { DialogWindow::LaunchOptions options; - options.dialogTitle = "Download JUCE version " + newVersion + "?"; + options.dialogTitle = "Download JUCE version " + newVersionString + "?"; options.resizable = false; - auto* content = new UpdateDialog (newVersion, releaseNotes); + auto* content = new UpdateDialog (newVersionString, releaseNotes); options.content.set (content, true); std::unique_ptr dialog (options.create()); @@ -292,66 +239,83 @@ private: DialogWindow* parentWindow = nullptr; }; -void LatestVersionCheckerAndUpdater::askUserForLocationToDownload() +void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const VersionInfo::Asset& asset) { - FileChooser chooser ("Please select the location into which you'd like to install the new version", + FileChooser chooser ("Please select the location into which you would like to install the new version", { getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get() }); if (chooser.browseForDirectory()) { auto targetFolder = chooser.getResult(); - if (isJUCEFolder (targetFolder)) + // By default we will install into 'targetFolder/JUCE', but we should install into + // 'targetFolder' if that is an existing JUCE directory. + bool willOverwriteJuceFolder = [&targetFolder] + { + if (isJUCEFolder (targetFolder)) + return true; + + targetFolder = targetFolder.getChildFile ("JUCE"); + + return isJUCEFolder (targetFolder); + }(); + + auto targetFolderPath = targetFolder.getFullPathName(); + + if (willOverwriteJuceFolder) { if (targetFolder.getChildFile (".git").isDirectory()) { AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Downloading New JUCE Version", - "This folder is a GIT repository!\n\nYou should use a \"git pull\" to update it to the latest version."); + targetFolderPath + "\n\nis a GIT repository!\n\nYou should use a \"git pull\" to update it to the latest version."); return; } if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Overwrite Existing JUCE Folder?", - String ("Do you want to overwrite the folder:\n\n" + targetFolder.getFullPathName() + "\n\n..with the latest version from juce.com?\n\n" - "This will move the existing folder to " + targetFolder.getFullPathName() + "_old."))) + "Do you want to replace the folder\n\n" + targetFolderPath + "\n\nwith the latest version from juce.com?\n\n" + "This will move the existing folder to " + targetFolderPath + "_old.")) { return; } } - else + else if (targetFolder.exists()) { - targetFolder = targetFolder.getChildFile ("JUCE").getNonexistentSibling(); + if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Existing File Or Directory", + "Do you want to move\n\n" + targetFolderPath + "\n\nto\n\n" + targetFolderPath + "_old?")) + { + return; + } } - downloadAndInstall (targetFolder); + downloadAndInstall (asset, targetFolder); } } -void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVersion, const String& releaseNotes) +void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVersionString, + const String& releaseNotes, + const VersionInfo::Asset& asset) { - if (newVersion.isNotEmpty() && releaseNotes.isNotEmpty() - && VersionHelpers::isNewVersion (VersionHelpers::getProductVersionString(), newVersion)) - { - dialogWindow = UpdateDialog::launchDialog (newVersion, releaseNotes); + dialogWindow = UpdateDialog::launchDialog (newVersionString, releaseNotes); - if (auto* mm = ModalComponentManager::getInstance()) - mm->attachCallback (dialogWindow.get(), ModalCallbackFunction::create ([this] (int result) - { - if (result == 1) - askUserForLocationToDownload(); + if (auto* mm = ModalComponentManager::getInstance()) + mm->attachCallback (dialogWindow.get(), + ModalCallbackFunction::create ([this, asset] (int result) + { + if (result == 1) + askUserForLocationToDownload (asset); - dialogWindow.reset(); - })); - } + dialogWindow.reset(); + })); } //============================================================================== class DownloadAndInstallThread : private ThreadWithProgressWindow { public: - DownloadAndInstallThread (const URL& u, const File& t, std::function&& cb) + DownloadAndInstallThread (const VersionInfo::Asset& a, const File& t, std::function&& cb) : ThreadWithProgressWindow ("Downloading New Version", true, true), - downloadURL (u), targetFolder (t), completionCallback (std::move (cb)) + asset (a), targetFolder (t), completionCallback (std::move (cb)) { launchThread (3); } @@ -368,7 +332,9 @@ private: result = install (zipData); if (result.failed()) - MessageManager::callAsync ([result] () { AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Installation Failed", result.getErrorMessage()); }); + MessageManager::callAsync ([result] { AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "Installation Failed", + result.getErrorMessage()); }); else MessageManager::callAsync (completionCallback); } @@ -378,10 +344,7 @@ private: setStatusMessage ("Downloading..."); int statusCode = 0; - StringPairArray responseHeaders; - - std::unique_ptr inStream (downloadURL.createInputStream (false, nullptr, nullptr, {}, 0, - &responseHeaders, &statusCode, 0)); + auto inStream = VersionInfo::createInputStreamForAsset (asset, statusCode); if (inStream != nullptr && statusCode == 200) { @@ -406,53 +369,53 @@ private: return Result::ok(); } - return Result::fail ("Failed to download from: " + downloadURL.toString (false)); + return Result::fail ("Failed to download from: " + asset.url); } - Result install (MemoryBlock& data) + Result install (const MemoryBlock& data) { setStatusMessage ("Installing..."); - auto result = unzipDownload (data); - - if (threadShouldExit()) - result = Result::fail ("Cancelled"); - - if (result.failed()) - return result; - - return Result::ok(); - } - - Result unzipDownload (const MemoryBlock& data) - { MemoryInputStream input (data, false); ZipFile zip (input); if (zip.getNumEntries() == 0) return Result::fail ("The downloaded file was not a valid JUCE file!"); - auto unzipTarget = File::createTempFile ({}); + struct ScopedDownloadFolder + { + ScopedDownloadFolder (const File& installTargetFolder) + { + folder = installTargetFolder.getSiblingFile (installTargetFolder.getFileNameWithoutExtension() + "_download").getNonexistentSibling(); + jassert (folder.createDirectory()); + } + + ~ScopedDownloadFolder() { folder.deleteRecursively(); } - if (! unzipTarget.createDirectory()) + File folder; + }; + + ScopedDownloadFolder unzipTarget (targetFolder); + + if (! unzipTarget.folder.isDirectory()) return Result::fail ("Couldn't create a temporary folder to unzip the new version!"); - auto r = zip.uncompressTo (unzipTarget); + auto r = zip.uncompressTo (unzipTarget.folder); if (r.failed()) - { - unzipTarget.deleteRecursively(); return r; - } + + if (threadShouldExit()) + return Result::fail ("Cancelled"); #if JUCE_LINUX || JUCE_MAC - r = setFilePermissions (unzipTarget, zip); + r = setFilePermissions (unzipTarget.folder, zip); if (r.failed()) - { - unzipTarget.deleteRecursively(); return r; - } + + if (threadShouldExit()) + return Result::fail ("Cancelled"); #endif if (targetFolder.exists()) @@ -460,21 +423,15 @@ private: auto oldFolder = targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling(); if (! targetFolder.moveFileTo (oldFolder)) - { - unzipTarget.deleteRecursively(); return Result::fail ("Could not remove the existing folder!\n\n" "This may happen if you are trying to download into a directory that requires administrator privileges to modify.\n" "Please select a folder that is writable by the current user."); - } } - if (! unzipTarget.moveFileTo (targetFolder)) - { - unzipTarget.deleteRecursively(); + if (! unzipTarget.folder.getChildFile ("JUCE").moveFileTo (targetFolder)) return Result::fail ("Could not overwrite the existing folder!\n\n" "This may happen if you are trying to download into a directory that requires administrator privileges to modify.\n" "Please select a folder that is writable by the current user."); - } return Result::ok(); } @@ -502,7 +459,7 @@ private: return Result::ok(); } - URL downloadURL; + VersionInfo::Asset asset; File targetFolder; std::function completionCallback; }; @@ -533,9 +490,9 @@ void restartProcess (const File& targetFolder) } } -void LatestVersionCheckerAndUpdater::downloadAndInstall (const File& targetFolder) +void LatestVersionCheckerAndUpdater::downloadAndInstall (const VersionInfo::Asset& asset, const File& targetFolder) { - installer.reset (new DownloadAndInstallThread ({ relativeDownloadPath }, targetFolder, + installer.reset (new DownloadAndInstallThread (asset, targetFolder, [this, targetFolder] { installer.reset(); diff --git a/extras/Projucer/Source/Application/jucer_AutoUpdater.h b/extras/Projucer/Source/Application/jucer_AutoUpdater.h index aa1949376f..2f4b288b58 100644 --- a/extras/Projucer/Source/Application/jucer_AutoUpdater.h +++ b/extras/Projucer/Source/Application/jucer_AutoUpdater.h @@ -26,6 +26,8 @@ #pragma once +#include "../Utility/Helpers/jucer_VersionInfo.h" + class DownloadAndInstallThread; class LatestVersionCheckerAndUpdater : public DeletedAtShutdown, @@ -43,17 +45,12 @@ public: private: //============================================================================== void run() override; - void queryUpdateServer(); - void processResult(); - void askUserAboutNewVersion (const String&, const String&); - void askUserForLocationToDownload(); - void downloadAndInstall (const File&); + void askUserAboutNewVersion (const String&, const String&, const VersionInfo::Asset&); + void askUserForLocationToDownload (const VersionInfo::Asset&); + void downloadAndInstall (const VersionInfo::Asset&, const File&); //============================================================================== bool showAlertWindows = false; - int statusCode = 0; - String relativeDownloadPath; - var jsonReply; std::unique_ptr installer; std::unique_ptr dialogWindow; diff --git a/extras/Projucer/Source/LiveBuildEngine/jucer_DownloadCompileEngineThread.cpp b/extras/Projucer/Source/LiveBuildEngine/jucer_DownloadCompileEngineThread.cpp index b3273dee2f..5b65750066 100644 --- a/extras/Projucer/Source/LiveBuildEngine/jucer_DownloadCompileEngineThread.cpp +++ b/extras/Projucer/Source/LiveBuildEngine/jucer_DownloadCompileEngineThread.cpp @@ -27,6 +27,7 @@ #include "../Application/jucer_Headers.h" #include "jucer_DownloadCompileEngineThread.h" #include "../LiveBuildEngine/jucer_CompileEngineDLL.h" +#include "../Utility/Helpers/jucer_VersionInfo.h" //============================================================================== bool DownloadCompileEngineThread::downloadAndInstall() @@ -83,38 +84,60 @@ void DownloadCompileEngineThread::run() Result DownloadCompileEngineThread::download (MemoryBlock& dest) { - int statusCode = 302; - const int timeoutMs = 10000; - StringPairArray responseHeaders; + auto info = VersionInfo::fetchFromUpdateServer (ProjectInfo::versionString); - URL url = getDownloadUrl(); - std::unique_ptr in (url.createInputStream (false, nullptr, nullptr, - String(), timeoutMs, &responseHeaders, - &statusCode, 0)); + if (info == nullptr) + return Result::fail ("Download error: cannot communicate with server"); - if (in == nullptr || statusCode != 200) - return Result::fail ("Download error: cannot establish connection"); + auto requiredAssetName = [] + { + String name ("JUCECompileEngine_"); - MemoryOutputStream mo (dest, true); + #if JUCE_MAC + name << "osx_"; + #elif JUCE_WINDOWS + name << "windows_"; + #else + jassertfalse; + #endif - int64 size = in->getTotalLength(); - int64 bytesReceived = -1; - String msg("Downloading... (123)"); + return name + ProjectInfo::versionString + ".zip"; + }(); - for (int64 pos = 0; pos < size; pos += bytesReceived) + for (auto& asset : info->assets) { - setStatusMessage (msg.replace ("123", File::descriptionOfSizeInBytes (pos))); + if (asset.name == requiredAssetName) + { + int statusCode = 0; + auto in = VersionInfo::createInputStreamForAsset (asset, statusCode); + + if (in == nullptr || statusCode != 200) + return Result::fail ("Download error: cannot establish connection"); + + MemoryOutputStream mo (dest, true); + + int64 size = in->getTotalLength(); + int64 bytesReceived = -1; + String msg("Downloading... (123)"); + + for (int64 pos = 0; pos < size; pos += bytesReceived) + { + setStatusMessage (msg.replace ("123", File::descriptionOfSizeInBytes (pos))); + + if (threadShouldExit()) + return Result::fail ("Download error: operation interrupted"); - if (threadShouldExit()) - return Result::fail ("Download error: operation interrupted"); + bytesReceived = mo.writeFromInputStream (*in, 8192); - bytesReceived = mo.writeFromInputStream (*in, 8192); + if (bytesReceived == 0) + return Result::fail ("Download error: lost connection"); + } - if (bytesReceived == 0) - return Result::fail ("Download error: lost connection"); + return Result::ok(); + } } - return Result::ok(); + return Result::fail ("Download error: no downloads available"); } Result DownloadCompileEngineThread::install (const MemoryBlock& data, File& targetFolder) @@ -131,21 +154,6 @@ Result DownloadCompileEngineThread::install (const MemoryBlock& data, File& targ return zip.uncompressTo (targetFolder); } -URL DownloadCompileEngineThread::getDownloadUrl() -{ - String urlStub ("http://assets.roli.com/juce/JUCECompileEngine_"); - - #if JUCE_MAC - urlStub << "osx_"; - #elif JUCE_WINDOWS - urlStub << "windows_"; - #else - jassertfalse; - #endif - - return urlStub + ProjectInfo::versionString + ".zip"; -} - File DownloadCompileEngineThread::getInstallFolder() { return CompileEngineDLL::getVersionedUserAppSupportFolder(); diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_VersionInfo.cpp b/extras/Projucer/Source/Utility/Helpers/jucer_VersionInfo.cpp new file mode 100644 index 0000000000..63cefab1ef --- /dev/null +++ b/extras/Projucer/Source/Utility/Helpers/jucer_VersionInfo.cpp @@ -0,0 +1,115 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include "../../Application/jucer_Headers.h" +#include "jucer_VersionInfo.h" + +std::unique_ptr VersionInfo::fetchFromUpdateServer (const String& versionString) +{ + return fetch ("tags/" + versionString); +} + +std::unique_ptr VersionInfo::fetchLatestFromUpdateServer() +{ + return fetch ("latest"); +} + +std::unique_ptr VersionInfo::createInputStreamForAsset (const Asset& asset, int& statusCode) +{ + URL downloadUrl (asset.url); + StringPairArray responseHeaders; + + return std::unique_ptr (downloadUrl.createInputStream (false, nullptr, nullptr, + "Accept: application/octet-stream", + 0, &responseHeaders, &statusCode, 1)); +} + +bool VersionInfo::isNewerVersionThanCurrent() +{ + jassert (versionString.isNotEmpty()); + + auto currentTokens = StringArray::fromTokens (ProjectInfo::versionString, ".", {}); + auto thisTokens = StringArray::fromTokens (versionString, ".", {}); + + jassert (thisTokens.size() == 3 && thisTokens.size() == 3); + + if (currentTokens[0].getIntValue() == thisTokens[0].getIntValue()) + { + if (currentTokens[1].getIntValue() == thisTokens[1].getIntValue()) + return currentTokens[2].getIntValue() < thisTokens[2].getIntValue(); + + return currentTokens[1].getIntValue() < thisTokens[1].getIntValue(); + } + + return currentTokens[0].getIntValue() < thisTokens[0].getIntValue(); +} + +std::unique_ptr VersionInfo::fetch (const String& endpoint) +{ + URL latestVersionURL ("https://api.github.com/repos/WeAreROLI/JUCE/releases/" + endpoint); + std::unique_ptr inStream (latestVersionURL.createInputStream (false)); + + if (inStream == nullptr) + return nullptr; + + auto content = inStream->readEntireStreamAsString(); + auto latestReleaseDetails = JSON::parse (content); + + auto* json = latestReleaseDetails.getDynamicObject(); + + if (json == nullptr) + return nullptr; + + auto versionString = json->getProperty ("tag_name").toString(); + + if (versionString.isEmpty()) + return nullptr; + + auto* assets = json->getProperty ("assets").getArray(); + + if (assets == nullptr) + return nullptr; + + auto releaseNotes = json->getProperty ("body").toString(); + std::vector parsedAssets; + + for (auto& asset : *assets) + { + if (auto* assetJson = asset.getDynamicObject()) + { + parsedAssets.push_back ({ assetJson->getProperty ("name").toString(), + assetJson->getProperty ("url").toString() }); + jassert (parsedAssets.back().name.isNotEmpty()); + jassert (parsedAssets.back().url.isNotEmpty()); + } + else + { + jassertfalse; + } + } + + return std::unique_ptr (new VersionInfo ({ versionString, releaseNotes, std::move (parsedAssets) })); +} diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_VersionInfo.h b/extras/Projucer/Source/Utility/Helpers/jucer_VersionInfo.h new file mode 100644 index 0000000000..68ec8e710b --- /dev/null +++ b/extras/Projucer/Source/Utility/Helpers/jucer_VersionInfo.h @@ -0,0 +1,54 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + + +//============================================================================== +class VersionInfo +{ +public: + struct Asset + { + const String name; + const String url; + }; + + static std::unique_ptr fetchFromUpdateServer (const String& versionString); + static std::unique_ptr fetchLatestFromUpdateServer(); + static std::unique_ptr createInputStreamForAsset (const Asset& asset, int& statusCode); + + bool isNewerVersionThanCurrent(); + + const String versionString; + const String releaseNotes; + const std::vector assets; + +private: + VersionInfo() = default; + + static std::unique_ptr fetch (const String&); +};