| @@ -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" | |||
| @@ -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 = "<group>"; | |||
| @@ -3455,6 +3475,7 @@ | |||
| 8BE478303CDF061B72F219E2, | |||
| BF913199032B4CE970E82AA3, | |||
| 25EF9B3FECB4C9F0F522DCAA, | |||
| 44AD0D81A65C5EAE3BE588FD, | |||
| 638C7247B6DBA67EFE46E124, | |||
| D0E26EB54B0087C8BE3D541E, | |||
| 468548FB21D264DC12321327, | |||
| @@ -233,6 +233,7 @@ | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_CodeHelpers.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_FileHelpers.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_MiscUtilities.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\UI\jucer_Icons.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\UI\jucer_JucerTreeViewBase.cpp"/> | |||
| @@ -1624,6 +1625,7 @@ | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_RelativePath.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_TranslationHelpers.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_ValueSourceHelpers.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\UI\PropertyComponents\jucer_ColourPropertyComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\UI\PropertyComponents\jucer_FilePathPropertyComponent.h"/> | |||
| @@ -496,6 +496,9 @@ | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_MiscUtilities.cpp"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.cpp"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.cpp"> | |||
| <Filter>Projucer\Utility\PIPs</Filter> | |||
| </ClCompile> | |||
| @@ -2325,6 +2328,9 @@ | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_ValueSourceHelpers.h"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.h"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.h"> | |||
| <Filter>Projucer\Utility\PIPs</Filter> | |||
| </ClInclude> | |||
| @@ -233,6 +233,7 @@ | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_CodeHelpers.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_FileHelpers.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_MiscUtilities.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\UI\jucer_Icons.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\UI\jucer_JucerTreeViewBase.cpp"/> | |||
| @@ -1624,6 +1625,7 @@ | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_RelativePath.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_TranslationHelpers.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_ValueSourceHelpers.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\UI\PropertyComponents\jucer_ColourPropertyComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\UI\PropertyComponents\jucer_FilePathPropertyComponent.h"/> | |||
| @@ -496,6 +496,9 @@ | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_MiscUtilities.cpp"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.cpp"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.cpp"> | |||
| <Filter>Projucer\Utility\PIPs</Filter> | |||
| </ClCompile> | |||
| @@ -2325,6 +2328,9 @@ | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_ValueSourceHelpers.h"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.h"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.h"> | |||
| <Filter>Projucer\Utility\PIPs</Filter> | |||
| </ClInclude> | |||
| @@ -233,6 +233,7 @@ | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_CodeHelpers.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_FileHelpers.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_MiscUtilities.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\UI\jucer_Icons.cpp"/> | |||
| <ClCompile Include="..\..\Source\Utility\UI\jucer_JucerTreeViewBase.cpp"/> | |||
| @@ -1624,6 +1625,7 @@ | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_RelativePath.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_TranslationHelpers.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_ValueSourceHelpers.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\UI\PropertyComponents\jucer_ColourPropertyComponent.h"/> | |||
| <ClInclude Include="..\..\Source\Utility\UI\PropertyComponents\jucer_FilePathPropertyComponent.h"/> | |||
| @@ -496,6 +496,9 @@ | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_MiscUtilities.cpp"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.cpp"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClCompile> | |||
| <ClCompile Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.cpp"> | |||
| <Filter>Projucer\Utility\PIPs</Filter> | |||
| </ClCompile> | |||
| @@ -2325,6 +2328,9 @@ | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_ValueSourceHelpers.h"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Utility\Helpers\jucer_VersionInfo.h"> | |||
| <Filter>Projucer\Utility\Helpers</Filter> | |||
| </ClInclude> | |||
| <ClInclude Include="..\..\Source\Utility\PIPs\jucer_PIPGenerator.h"> | |||
| <Filter>Projucer\Utility\PIPs</Filter> | |||
| </ClInclude> | |||
| @@ -636,6 +636,10 @@ | |||
| file="Source/Utility/Helpers/jucer_TranslationHelpers.h"/> | |||
| <FILE id="EuC4K4" name="jucer_ValueSourceHelpers.h" compile="0" resource="0" | |||
| file="Source/Utility/Helpers/jucer_ValueSourceHelpers.h"/> | |||
| <FILE id="BPCoKV" name="jucer_VersionInfo.cpp" compile="1" resource="0" | |||
| file="Source/Utility/Helpers/jucer_VersionInfo.cpp"/> | |||
| <FILE id="TnBQtv" name="jucer_VersionInfo.h" compile="0" resource="0" | |||
| file="Source/Utility/Helpers/jucer_VersionInfo.h"/> | |||
| </GROUP> | |||
| <GROUP id="{A07C4A97-0855-5346-CAF2-A005580B6773}" name="PIPs"> | |||
| <FILE id="joAnDa" name="jucer_PIPGenerator.cpp" compile="1" resource="0" | |||
| @@ -52,119 +52,65 @@ void LatestVersionCheckerAndUpdater::checkForNewVersion (bool showAlerts) | |||
| //============================================================================== | |||
| void LatestVersionCheckerAndUpdater::run() | |||
| { | |||
| queryUpdateServer(); | |||
| auto info = VersionInfo::fetchLatestFromUpdateServer(); | |||
| if (! threadShouldExit()) | |||
| MessageManager::callAsync ([this] { processResult(); }); | |||
| } | |||
| //============================================================================== | |||
| String getOSString() | |||
| { | |||
| #if JUCE_MAC | |||
| return "OSX"; | |||
| #elif JUCE_WINDOWS | |||
| return "Windows"; | |||
| #elif JUCE_LINUX | |||
| return "Linux"; | |||
| #else | |||
| jassertfalse; | |||
| return "Unknown"; | |||
| #endif | |||
| } | |||
| namespace VersionHelpers | |||
| { | |||
| String formatProductVersion (int versionNum) | |||
| if (info == nullptr) | |||
| { | |||
| int major = (versionNum & 0xff0000) >> 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<InputStream> 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<DialogWindow> launchDialog (const String& newVersion, const String& releaseNotes) | |||
| static std::unique_ptr<DialogWindow> 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<DialogWindow> 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<void()>&& cb) | |||
| DownloadAndInstallThread (const VersionInfo::Asset& a, const File& t, std::function<void()>&& 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<InputStream> 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<void()> 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(); | |||
| @@ -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<DownloadAndInstallThread> installer; | |||
| std::unique_ptr<Component> dialogWindow; | |||
| @@ -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<InputStream> 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(); | |||
| @@ -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> VersionInfo::fetchFromUpdateServer (const String& versionString) | |||
| { | |||
| return fetch ("tags/" + versionString); | |||
| } | |||
| std::unique_ptr<VersionInfo> VersionInfo::fetchLatestFromUpdateServer() | |||
| { | |||
| return fetch ("latest"); | |||
| } | |||
| std::unique_ptr<InputStream> VersionInfo::createInputStreamForAsset (const Asset& asset, int& statusCode) | |||
| { | |||
| URL downloadUrl (asset.url); | |||
| StringPairArray responseHeaders; | |||
| return std::unique_ptr<InputStream> (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> VersionInfo::fetch (const String& endpoint) | |||
| { | |||
| URL latestVersionURL ("https://api.github.com/repos/WeAreROLI/JUCE/releases/" + endpoint); | |||
| std::unique_ptr<InputStream> 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<VersionInfo::Asset> 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<VersionInfo> (new VersionInfo ({ versionString, releaseNotes, std::move (parsedAssets) })); | |||
| } | |||
| @@ -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<VersionInfo> fetchFromUpdateServer (const String& versionString); | |||
| static std::unique_ptr<VersionInfo> fetchLatestFromUpdateServer(); | |||
| static std::unique_ptr<InputStream> createInputStreamForAsset (const Asset& asset, int& statusCode); | |||
| bool isNewerVersionThanCurrent(); | |||
| const String versionString; | |||
| const String releaseNotes; | |||
| const std::vector<Asset> assets; | |||
| private: | |||
| VersionInfo() = default; | |||
| static std::unique_ptr<VersionInfo> fetch (const String&); | |||
| }; | |||