From b5ab6ff43106a3e2ec8276dab560a79efd25f6f7 Mon Sep 17 00:00:00 2001 From: Anjal Doshi Date: Wed, 29 Mar 2023 17:56:22 -0700 Subject: [PATCH 1/6] Initial support for auto updating the GUI Add support for checking and downloading latest version of the GUI --- CMakeLists.txt | 2 +- Source/AutoUpdater.cpp | 548 ++++++++++++++++++++++++++++++++++++++ Source/AutoUpdater.h | 62 +++++ Source/CMakeLists.txt | 2 + Source/UI/UIComponent.cpp | 14 + Source/UI/UIComponent.h | 1 + 6 files changed, 628 insertions(+), 1 deletion(-) create mode 100644 Source/AutoUpdater.cpp create mode 100644 Source/AutoUpdater.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f4e37a0b94..f372e0a848 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ #Open Ephys GUI main build file cmake_minimum_required(VERSION 3.15) -set(GUI_VERSION 0.6.4) +set(GUI_VERSION 0.6.3) string(REGEX MATCHALL "[0-9]+" VERSION_LIST ${GUI_VERSION}) set(GUI_VERSION_HEX "0x") diff --git a/Source/AutoUpdater.cpp b/Source/AutoUpdater.cpp new file mode 100644 index 0000000000..97102c227e --- /dev/null +++ b/Source/AutoUpdater.cpp @@ -0,0 +1,548 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2023 Open Ephys + + ------------------------------------------------------------------ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Reference : https://github.com/juce-framework/JUCE/blob/6.0.8/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp + +*/ + +#include "AutoUpdater.h" +#include "CoreServices.h" + +//============================================================================== +LatestVersionCheckerAndUpdater::LatestVersionCheckerAndUpdater() + : Thread ("VersionChecker") +{ +} + +LatestVersionCheckerAndUpdater::~LatestVersionCheckerAndUpdater() +{ + stopThread (6000); + clearSingletonInstance(); +} + +void LatestVersionCheckerAndUpdater::checkForNewVersion (bool background) +{ + if (! isThreadRunning()) + { + backgroundCheck = background; + startThread (3); + } +} + +//============================================================================== +void LatestVersionCheckerAndUpdater::run() +{ + LOGC("Checking for a new version...."); + URL latestVersionURL ("https://api.github.com/repos/open-ephys/plugin-GUI/releases/latest"); + + std::unique_ptr inStream (latestVersionURL.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress) + .withConnectionTimeoutMs (5000))); + const String commErr = "Failed to communicate with the Open Ephys update server.\n" + "Please try again in a few minutes.\n\n" + "If this problem persists you can download the latest version of Open Ephys GUI from open-ephys.org/gui"; + + if (inStream == nullptr) + { + if (! backgroundCheck) + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "Update Server Communication Error", + commErr); + + return; + } + + auto content = inStream->readEntireStreamAsString(); + auto latestReleaseDetails = JSON::parse (content); + + auto* json = latestReleaseDetails.getDynamicObject(); + + if (json == nullptr) + { + if (! backgroundCheck) + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "Update Server Communication Error", + commErr); + + return; + } + + auto versionString = json->getProperty ("tag_name").toString(); + + if (versionString.isEmpty()) + return; + + auto* assets = json->getProperty ("assets").getArray(); + + if (assets == nullptr) + return; + + 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(), + (int)assetJson->getProperty("size")}); + jassert (parsedAssets.back().name.isNotEmpty()); + jassert (parsedAssets.back().url.isNotEmpty()); + jassert (parsedAssets.back().size != 0); + + } + else + { + jassertfalse; + } + } + + String latestVer = versionString.substring(1); + + + if (latestVer.compareNatural(CoreServices::getGUIVersion()) <= 0) + { + if (! backgroundCheck) + AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, + "No New Version Available", + "Your GUI version is up to date."); + return; + } + + auto osString = [] + { + #if JUCE_MAC + return "mac"; + #elif JUCE_WINDOWS + return "windows"; + #elif JUCE_LINUX + return "linux"; + #else + jassertfalse; + return "Unknown"; + #endif + }(); + +#if JUCE_WINDOWS + String requiredFilename ("open-ephys-" + versionString + "-" + osString + ".zip"); + File exeDir = File::getSpecialLocation(File::SpecialLocationType::currentExecutableFile).getParentDirectory(); + if(exeDir.findChildFiles(File::findFiles, false, "unins*").size() > 0) + { + requiredFilename = "Install-Open-Ephys-GUI-" + versionString + ".exe"; + } +#elif + String requiredFilename ("open-ephys-" + versionString + "-" + osString + ".zip"); +#endif + for (auto& asset : parsedAssets) + { + if (asset.name == requiredFilename) + { + + MessageManager::callAsync ([this, versionString, releaseNotes, asset] + { + askUserAboutNewVersion (versionString, releaseNotes, asset); + }); + + return; + } + } + + if (! backgroundCheck) + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "Failed to find any new downloads", + "Please try again in a few minutes."); +} + +//============================================================================== +class UpdateDialog : public Component +{ +public: + UpdateDialog (const String& newVersion, const String& releaseNotes) + { + titleLabel.setText ("Open Ephys GUI version " + newVersion, dontSendNotification); + titleLabel.setFont (Font("Fira Sans", "SemiBold", 18.0f)); + titleLabel.setJustificationType (Justification::centred); + addAndMakeVisible (titleLabel); + + contentLabel.setText ("A new version of Open Ephys GUI is available - would you like to download it?", dontSendNotification); + contentLabel.setFont (Font("Fira Sans", "Regular", 16.0f)); + contentLabel.setJustificationType (Justification::topLeft); + contentLabel.setMinimumHorizontalScale(1.0); + addAndMakeVisible (contentLabel); + + releaseNotesEditor.setMultiLine (true); + releaseNotesEditor.setReadOnly (true); + releaseNotesEditor.setText (releaseNotes); + addAndMakeVisible (releaseNotesEditor); + + addAndMakeVisible (chooseButton); + chooseButton.onClick = [this] { exitModalStateWithResult (1); }; + + addAndMakeVisible (cancelButton); + cancelButton.onClick = [this] + { + // ProjucerApplication::getApp().setAutomaticVersionCheckingEnabled (! dontAskAgainButton.getToggleState()); + exitModalStateWithResult (-1); + }; + + dontAskAgainButton.setToggleState (false, dontSendNotification); + addAndMakeVisible (dontAskAgainButton); + +#if JUCE_MAC + File iconDir = File::getSpecialLocation(File::currentApplicationFile).getChildFile("Contents/Resources"); +#else + File iconDir = File::getSpecialLocation(File::currentApplicationFile).getParentDirectory(); +#endif + juceIcon = Drawable::createFromImageFile(iconDir.getChildFile("icon-small.png")); + lookAndFeelChanged(); + + setSize (640, 480); + } + + void resized() override + { + auto b = getLocalBounds().reduced (10); + + auto topSlice = b.removeFromTop (juceIconBounds.getHeight()) + .withTrimmedLeft (juceIconBounds.getWidth()); + + titleLabel.setBounds (topSlice.removeFromTop (25)); + topSlice.removeFromTop (5); + contentLabel.setBounds (topSlice.removeFromTop (25)); + + auto buttonBounds = b.removeFromBottom (60); + buttonBounds.removeFromBottom (25); + chooseButton.setBounds (buttonBounds.removeFromLeft (buttonBounds.getWidth() / 2).reduced (20, 0)); + cancelButton.setBounds (buttonBounds.reduced (20, 0)); + dontAskAgainButton.setBounds (cancelButton.getBounds().withY (cancelButton.getBottom() + 5).withHeight (20)); + + releaseNotesEditor.setBounds (b.reduced (0, 10)); + } + + void paint (Graphics& g) override + { + g.fillAll (Colours::lightgrey); + + if (juceIcon != nullptr) + juceIcon->drawWithin (g, juceIconBounds.toFloat(), + RectanglePlacement::stretchToFit, 1.0f); + } + + static std::unique_ptr launchDialog (const String& newVersionString, + const String& releaseNotes) + { + DialogWindow::LaunchOptions options; + + options.dialogTitle = "Download Open Ephys GUI version " + newVersionString + "?"; + options.resizable = false; + + auto* content = new UpdateDialog (newVersionString, releaseNotes); + options.content.set (content, true); + + std::unique_ptr dialog (options.create()); + + content->setParentWindow (dialog.get()); + dialog->enterModalState (true, nullptr, true); + + return dialog; + } + +private: + void lookAndFeelChanged() override + { + cancelButton.setColour (TextButton::buttonColourId, Colours::crimson); + releaseNotesEditor.applyFontToAllText (Font("Fira Sans", "Regular", 16.0f)); + } + + void setParentWindow (DialogWindow* parent) + { + parentWindow = parent; + } + + void exitModalStateWithResult (int result) + { + if (parentWindow != nullptr) + parentWindow->exitModalState (result); + } + + Label titleLabel, contentLabel, releaseNotesLabel; + TextEditor releaseNotesEditor; + TextButton chooseButton { "Download" }, cancelButton { "Cancel" }; + ToggleButton dontAskAgainButton { "Don't ask again" }; + std::unique_ptr juceIcon; + Rectangle juceIconBounds { 10, 10, 64, 64 }; + + DialogWindow* parentWindow = nullptr; +}; + +void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const Asset& asset) +{ + FileChooser chooser ("Please select the location into which you would like to install the new version", + { File::getSpecialLocation(File::userDesktopDirectory) }, + "*.exe;*.zip"); + + if (chooser.browseForDirectory()) + { + auto targetFolder = chooser.getResult(); + if (targetFolder == File{}) + return; + + downloadAndInstall (asset, targetFolder); + } +} + +void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVersionString, + const String& releaseNotes, + const Asset& asset) +{ + dialogWindow = UpdateDialog::launchDialog (newVersionString, releaseNotes); + + if (auto* mm = ModalComponentManager::getInstance()) + { + mm->attachCallback (dialogWindow.get(), + ModalCallbackFunction::create ([this, asset] (int result) + { + if (result == 1) + askUserForLocationToDownload (asset); + + dialogWindow.reset(); + })); + } +} + +//============================================================================== +class DownloadAndInstallThread : private ThreadWithProgressWindow +{ +public: + DownloadAndInstallThread (const LatestVersionCheckerAndUpdater::Asset& a, + const File& t, + std::function&& cb) + : ThreadWithProgressWindow ("Downloading New Version", true, true), + asset (a), targetFolder (t), completionCallback (std::move (cb)) + { + launchThread (3); + } + +private: + void run() override + { + setProgress (0.0); + + File targetFile = targetFolder.getChildFile(asset.name).getNonexistentSibling(); + auto result = download (targetFile); + + // if (result.wasOk() && ! threadShouldExit()) + // result = install (zipData); + + if (result.failed()) + { + MessageManager::callAsync ([result] { AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "Downloading Failed", + result.getErrorMessage()); }); + } + else + { + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "Download finished!", + "New binaries can be found at: " + + targetFile.getFullPathName()); + MessageManager::callAsync (completionCallback); + } + } + + Result download (File& dest) + { + setStatusMessage ("Downloading..."); + + int statusCode = 0; + URL downloadUrl (asset.url); + StringPairArray responseHeaders; + + auto inStream = downloadUrl.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress) + .withExtraHeaders ("Accept: application/octet-stream") + .withConnectionTimeoutMs (5000) + .withResponseHeaders (&responseHeaders) + .withStatusCode (&statusCode) + .withNumRedirectsToFollow (1)); + + if (inStream != nullptr && statusCode == 200) + { + int64 total = 0; + + //Use the Url's input stream and write it to a file using output stream + std::unique_ptr out = dest.createOutputStream(); + + for (;;) + { + if (threadShouldExit()) + return Result::fail ("Cancelled"); + + + + auto written = out->writeFromInputStream(*inStream, 8192); + + if (written == 0) + break; + + total += written; + + setProgress((double)total / (double)asset.size); + + setStatusMessage ("Downloading... " + File::descriptionOfSizeInBytes (total)); + } + + out->flush(); + return Result::ok(); + } + + return Result::fail ("Failed to download from: " + asset.url); + } + + Result install (const File& dest) + { + setStatusMessage ("Installing..."); + + ZipFile zip (dest); + + // if (zip.getNumEntries() == 0) + // return Result::fail ("The downloaded file was not a valid JUCE file!"); + + // struct ScopedDownloadFolder + // { + // explicit ScopedDownloadFolder (const File& installTargetFolder) + // { + // folder = installTargetFolder.getSiblingFile (installTargetFolder.getFileNameWithoutExtension() + "_download").getNonexistentSibling(); + // jassert (folder.createDirectory()); + // } + + // ~ScopedDownloadFolder() { folder.deleteRecursively(); } + + // 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.folder); + + // if (r.failed()) + // return r; + + // if (threadShouldExit()) + // return Result::fail ("Cancelled"); + + // #if JUCE_LINUX || JUCE_BSD || JUCE_MAC + // r = setFilePermissions (unzipTarget.folder, zip); + + // if (r.failed()) + // return r; + + // if (threadShouldExit()) + // return Result::fail ("Cancelled"); + // #endif + + // if (targetFolder.exists()) + // { + // auto oldFolder = targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling(); + + // if (! targetFolder.moveFileTo (oldFolder)) + // 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.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(); + } + + Result setFilePermissions (const File& root, const ZipFile& zip) + { + // constexpr uint32 executableFlag = (1 << 22); + + // for (int i = 0; i < zip.getNumEntries(); ++i) + // { + // auto* entry = zip.getEntry (i); + + // if ((entry->externalFileAttributes & executableFlag) != 0 && entry->filename.getLastCharacter() != '/') + // { + // auto exeFile = root.getChildFile (entry->filename); + + // if (! exeFile.exists()) + // return Result::fail ("Failed to find executable file when setting permissions " + exeFile.getFileName()); + + // if (! exeFile.setExecutePermission (true)) + // return Result::fail ("Failed to set executable file permission for " + exeFile.getFileName()); + // } + // } + + // return Result::ok(); + } + + const LatestVersionCheckerAndUpdater::Asset asset; + File targetFolder; + std::function completionCallback; +}; + +static void restartProcess (const File& targetFolder) +{ + #if JUCE_MAC || JUCE_LINUX || JUCE_BSD + #if JUCE_MAC + auto newProcess = targetFolder.getChildFile ("Projucer.app").getChildFile ("Contents").getChildFile ("MacOS").getChildFile ("Projucer"); + #elif JUCE_LINUX || JUCE_BSD + auto newProcess = targetFolder.getChildFile ("Projucer"); + #endif + + StringArray command ("/bin/sh", "-c", "while killall -0 Projucer; do sleep 5; done; " + newProcess.getFullPathName().quoted()); + #elif JUCE_WINDOWS + auto newProcess = targetFolder.getChildFile ("Projucer.exe"); + + auto command = "cmd.exe /c\"@echo off & for /l %a in (0) do ( tasklist | find \"Projucer\" >nul & ( if errorlevel 1 ( " + + targetFolder.getChildFile ("Projucer.exe").getFullPathName().quoted() + " & exit /b ) else ( timeout /t 10 >nul ) ) )\""; + #endif + + if (newProcess.existsAsFile()) + { + ChildProcess restartProcess; + restartProcess.start (command, 0); + + JUCEApplication::getInstance()->systemRequestedQuit(); + } +} + +void LatestVersionCheckerAndUpdater::downloadAndInstall (const Asset& asset, const File& targetFolder) +{ + installer.reset (new DownloadAndInstallThread (asset, targetFolder, + [this, targetFolder] + { + installer.reset(); + + // restartProcess (targetFolder); + })); +} + +//============================================================================== +JUCE_IMPLEMENT_SINGLETON (LatestVersionCheckerAndUpdater) diff --git a/Source/AutoUpdater.h b/Source/AutoUpdater.h new file mode 100644 index 0000000000..a637b39221 --- /dev/null +++ b/Source/AutoUpdater.h @@ -0,0 +1,62 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2023 Open Ephys + + ------------------------------------------------------------------ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Reference : https://github.com/juce-framework/JUCE/blob/6.0.8/extras/Projucer/Source/Application/jucer_AutoUpdater.h + +*/ + +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" + +class DownloadAndInstallThread; + +class LatestVersionCheckerAndUpdater : public DeletedAtShutdown, + private Thread +{ +public: + LatestVersionCheckerAndUpdater(); + ~LatestVersionCheckerAndUpdater() override; + + struct Asset + { + const String name; + const String url; + const int size; + }; + + void checkForNewVersion (bool isBackgroundCheck); + + //============================================================================== + JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (LatestVersionCheckerAndUpdater) + +private: + //============================================================================== + void run() override; + void askUserAboutNewVersion (const String&, const String&, const Asset& asset); + void askUserForLocationToDownload (const Asset& asset); + void downloadAndInstall (const Asset& asset, const File& targetFolder); + + //============================================================================== + bool backgroundCheck = false; + + std::unique_ptr installer; + std::unique_ptr dialogWindow; +}; diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 543150a832..4b33825166 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -4,6 +4,8 @@ add_sources(open-ephys AccessClass.h AccessClass.cpp + AutoUpdater.cpp + AutoUpdater.h CoreServices.h CoreServices.cpp MainWindow.h diff --git a/Source/UI/UIComponent.cpp b/Source/UI/UIComponent.cpp index 5e33a45a74..306e4e72b6 100755 --- a/Source/UI/UIComponent.cpp +++ b/Source/UI/UIComponent.cpp @@ -36,6 +36,7 @@ #include "../Processors/ProcessorGraph/ProcessorGraph.h" #include "../Audio/AudioComponent.h" #include "../MainWindow.h" +#include "../AutoUpdater.h" UIComponent::UIComponent(MainWindow* mainWindow_, ProcessorGraph* pgraph, AudioComponent* audio_) : mainWindow(mainWindow_), processorGraph(pgraph), audio(audio_), messageCenterIsCollapsed(true) @@ -476,6 +477,7 @@ PopupMenu UIComponent::getMenuForIndex(int menuIndex, const String& menuName) else if (menuIndex == 3) { menu.addCommandItem(commandManager, showHelp); + menu.addCommandItem(commandManager, checkForUpdates); } return menu; @@ -518,6 +520,7 @@ void UIComponent::getAllCommands(Array & commands) setClockModeDefault, setClockModeHHMMSS, showHelp, + checkForUpdates, resizeWindow, openPluginInstaller, openDefaultConfigWindow @@ -651,6 +654,11 @@ void UIComponent::getCommandInfo(CommandID commandID, ApplicationCommandInfo& re result.setActive(true); break; + case checkForUpdates: + result.setInfo("Check for updates", "Checks if a newer version of the GUI is available", "General", 0); + result.setActive(true); + break; + case resizeWindow: result.setInfo("Reset window bounds", "Reset window bounds", "General", 0); break; @@ -852,6 +860,12 @@ bool UIComponent::perform(const InvocationInfo& info) url.launchInDefaultBrowser(); break; } + + case checkForUpdates: + { + LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (false); + break; + } case toggleProcessorList: processorList->toggleState(); diff --git a/Source/UI/UIComponent.h b/Source/UI/UIComponent.h index 7dca1d685e..c21399a52a 100755 --- a/Source/UI/UIComponent.h +++ b/Source/UI/UIComponent.h @@ -206,6 +206,7 @@ class UIComponent : public Component, setClockModeHHMMSS = 0x2112, toggleHttpServer = 0x4001, showHelp = 0x2011, + checkForUpdates = 0x2022, resizeWindow = 0x2012, reloadOnStartup = 0x2013, saveSignalChainAs = 0x2014, From bbcfd6a627592b13125255c16c519f7ed27b03b3 Mon Sep 17 00:00:00 2001 From: anjaldoshi Date: Thu, 30 Mar 2023 14:42:29 -0700 Subject: [PATCH 2/6] Identify installer vs zip installation and run installer if required --- Source/AutoUpdater.cpp | 210 +++++++++++++++-------------------------- Source/AutoUpdater.h | 6 +- 2 files changed, 77 insertions(+), 139 deletions(-) diff --git a/Source/AutoUpdater.cpp b/Source/AutoUpdater.cpp index 97102c227e..6a937d000e 100644 --- a/Source/AutoUpdater.cpp +++ b/Source/AutoUpdater.cpp @@ -24,6 +24,10 @@ #include "AutoUpdater.h" #include "CoreServices.h" +#ifdef _WIN32 +#include +#include +#endif //============================================================================== LatestVersionCheckerAndUpdater::LatestVersionCheckerAndUpdater() @@ -141,16 +145,16 @@ void LatestVersionCheckerAndUpdater::run() #endif }(); -#if JUCE_WINDOWS String requiredFilename ("open-ephys-" + versionString + "-" + osString + ".zip"); + +#if JUCE_WINDOWS File exeDir = File::getSpecialLocation(File::SpecialLocationType::currentExecutableFile).getParentDirectory(); if(exeDir.findChildFiles(File::findFiles, false, "unins*").size() > 0) { requiredFilename = "Install-Open-Ephys-GUI-" + versionString + ".exe"; } -#elif - String requiredFilename ("open-ephys-" + versionString + "-" + osString + ".zip"); #endif + for (auto& asset : parsedAssets) { if (asset.name == requiredFilename) @@ -288,7 +292,7 @@ class UpdateDialog : public Component TextButton chooseButton { "Download" }, cancelButton { "Cancel" }; ToggleButton dontAskAgainButton { "Don't ask again" }; std::unique_ptr juceIcon; - Rectangle juceIconBounds { 10, 10, 64, 64 }; + juce::Rectangle juceIconBounds { 10, 10, 64, 64 }; DialogWindow* parentWindow = nullptr; }; @@ -305,7 +309,9 @@ void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const Asset& if (targetFolder == File{}) return; - downloadAndInstall (asset, targetFolder); + File targetFile = targetFolder.getChildFile(asset.name).getNonexistentSibling(); + + downloadAndInstall (asset, targetFile); } } @@ -329,14 +335,14 @@ void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVe } //============================================================================== -class DownloadAndInstallThread : private ThreadWithProgressWindow +class DownloadThread : private ThreadWithProgressWindow { public: - DownloadAndInstallThread (const LatestVersionCheckerAndUpdater::Asset& a, - const File& t, - std::function&& cb) + DownloadThread (const LatestVersionCheckerAndUpdater::Asset& a, + const File& t, + std::function&& cb) : ThreadWithProgressWindow ("Downloading New Version", true, true), - asset (a), targetFolder (t), completionCallback (std::move (cb)) + asset (a), targetFile (t), completionCallback (std::move (cb)) { launchThread (3); } @@ -346,24 +352,17 @@ class DownloadAndInstallThread : private ThreadWithProgressWindow { setProgress (0.0); - File targetFile = targetFolder.getChildFile(asset.name).getNonexistentSibling(); auto result = download (targetFile); - // if (result.wasOk() && ! threadShouldExit()) - // result = install (zipData); - if (result.failed()) { - MessageManager::callAsync ([result] { AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, - "Downloading Failed", - result.getErrorMessage()); }); + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "Downloading Failed", + result.getErrorMessage()); } else { - AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, - "Download finished!", - "New binaries can be found at: " + - targetFile.getFullPathName()); + setProgress (-1.0); MessageManager::callAsync (completionCallback); } } @@ -406,7 +405,10 @@ class DownloadAndInstallThread : private ThreadWithProgressWindow setProgress((double)total / (double)asset.size); - setStatusMessage ("Downloading... " + File::descriptionOfSizeInBytes (total)); + setStatusMessage ("Downloading... " + + File::descriptionOfSizeInBytes (total) + + " / " + + File::descriptionOfSizeInBytes (asset.size)); } out->flush(); @@ -416,132 +418,68 @@ class DownloadAndInstallThread : private ThreadWithProgressWindow return Result::fail ("Failed to download from: " + asset.url); } - Result install (const File& dest) - { - setStatusMessage ("Installing..."); - - ZipFile zip (dest); - - // if (zip.getNumEntries() == 0) - // return Result::fail ("The downloaded file was not a valid JUCE file!"); - - // struct ScopedDownloadFolder - // { - // explicit ScopedDownloadFolder (const File& installTargetFolder) - // { - // folder = installTargetFolder.getSiblingFile (installTargetFolder.getFileNameWithoutExtension() + "_download").getNonexistentSibling(); - // jassert (folder.createDirectory()); - // } - - // ~ScopedDownloadFolder() { folder.deleteRecursively(); } - - // 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.folder); - - // if (r.failed()) - // return r; - - // if (threadShouldExit()) - // return Result::fail ("Cancelled"); - - // #if JUCE_LINUX || JUCE_BSD || JUCE_MAC - // r = setFilePermissions (unzipTarget.folder, zip); - - // if (r.failed()) - // return r; - - // if (threadShouldExit()) - // return Result::fail ("Cancelled"); - // #endif - - // if (targetFolder.exists()) - // { - // auto oldFolder = targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling(); - - // if (! targetFolder.moveFileTo (oldFolder)) - // 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.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(); - } - - Result setFilePermissions (const File& root, const ZipFile& zip) - { - // constexpr uint32 executableFlag = (1 << 22); - - // for (int i = 0; i < zip.getNumEntries(); ++i) - // { - // auto* entry = zip.getEntry (i); - - // if ((entry->externalFileAttributes & executableFlag) != 0 && entry->filename.getLastCharacter() != '/') - // { - // auto exeFile = root.getChildFile (entry->filename); - - // if (! exeFile.exists()) - // return Result::fail ("Failed to find executable file when setting permissions " + exeFile.getFileName()); - - // if (! exeFile.setExecutePermission (true)) - // return Result::fail ("Failed to set executable file permission for " + exeFile.getFileName()); - // } - // } - - // return Result::ok(); - } - const LatestVersionCheckerAndUpdater::Asset asset; - File targetFolder; + File targetFile; std::function completionCallback; }; -static void restartProcess (const File& targetFolder) +static void runInstaller (const File& targetFile) { - #if JUCE_MAC || JUCE_LINUX || JUCE_BSD - #if JUCE_MAC - auto newProcess = targetFolder.getChildFile ("Projucer.app").getChildFile ("Contents").getChildFile ("MacOS").getChildFile ("Projucer"); - #elif JUCE_LINUX || JUCE_BSD - auto newProcess = targetFolder.getChildFile ("Projucer"); - #endif - - StringArray command ("/bin/sh", "-c", "while killall -0 Projucer; do sleep 5; done; " + newProcess.getFullPathName().quoted()); - #elif JUCE_WINDOWS - auto newProcess = targetFolder.getChildFile ("Projucer.exe"); - - auto command = "cmd.exe /c\"@echo off & for /l %a in (0) do ( tasklist | find \"Projucer\" >nul & ( if errorlevel 1 ( " - + targetFolder.getChildFile ("Projucer.exe").getFullPathName().quoted() + " & exit /b ) else ( timeout /t 10 >nul ) ) )\""; - #endif - - if (newProcess.existsAsFile()) + bool runInstaller = AlertWindow::showOkCancelBox(AlertWindow::WarningIcon, + "Quit Open Ephys GUI?", + "To run the installer, the current instance of GUI needs to be closed." + "\nAre you sure you want to continue?", + "Yes", "No"); + + if(runInstaller) { - ChildProcess restartProcess; - restartProcess.start (command, 0); + #if JUCE_WINDOWS + if (targetFile.existsAsFile()) + { + auto returnCode = ShellExecute(NULL, (LPCSTR)"runas", targetFile.getFullPathName().toRawUTF8(), NULL, NULL, SW_SHOW); - JUCEApplication::getInstance()->systemRequestedQuit(); + if((int)returnCode > 31) + JUCEApplication::getInstance()->systemRequestedQuit(); + else + LOGE("Failed to run the installer: ", GetLastError()); + } + #endif } } -void LatestVersionCheckerAndUpdater::downloadAndInstall (const Asset& asset, const File& targetFolder) +void LatestVersionCheckerAndUpdater::downloadAndInstall (const Asset& asset, const File& targetFile) { - installer.reset (new DownloadAndInstallThread (asset, targetFolder, - [this, targetFolder] - { - installer.reset(); +#if JUCE_WINDOWS + File exeDir = File::getSpecialLocation( + File::SpecialLocationType::currentExecutableFile).getParentDirectory(); + + if(exeDir.findChildFiles(File::findFiles, false, "unins*").size() > 0) + { + downloader.reset (new DownloadThread (asset, targetFile, + [this, targetFile] + { + downloader.reset(); + runInstaller(targetFile); + + })); + } + else +#endif + { + AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, + "Download successful!", + "Please extract the zip file located at: \n" + + targetFile.getFullPathName().quoted() + + "\nto your desired location and then run the updated version from there. " + "You can also overwrite the current installation after quitting the current instance."); + + downloader.reset (new DownloadThread (asset, targetFile, + [this] + { + downloader.reset(); - // restartProcess (targetFolder); - })); + })); + } } //============================================================================== diff --git a/Source/AutoUpdater.h b/Source/AutoUpdater.h index a637b39221..5b9948496b 100644 --- a/Source/AutoUpdater.h +++ b/Source/AutoUpdater.h @@ -26,7 +26,7 @@ #include "../JuceLibraryCode/JuceHeader.h" -class DownloadAndInstallThread; +class DownloadThread; class LatestVersionCheckerAndUpdater : public DeletedAtShutdown, private Thread @@ -52,11 +52,11 @@ class LatestVersionCheckerAndUpdater : public DeletedAtShutdown, void run() override; void askUserAboutNewVersion (const String&, const String&, const Asset& asset); void askUserForLocationToDownload (const Asset& asset); - void downloadAndInstall (const Asset& asset, const File& targetFolder); + void downloadAndInstall (const Asset& asset, const File& targetFile); //============================================================================== bool backgroundCheck = false; - std::unique_ptr installer; + std::unique_ptr downloader; std::unique_ptr dialogWindow; }; From 20b3e9d7a08fc0eff0d2438a4873eec105ea2eb7 Mon Sep 17 00:00:00 2001 From: anjaldoshi Date: Thu, 30 Mar 2023 16:12:40 -0700 Subject: [PATCH 3/6] Fix Alert Window showing up before download completes --- Source/AutoUpdater.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Source/AutoUpdater.cpp b/Source/AutoUpdater.cpp index 6a937d000e..e3c38d1f35 100644 --- a/Source/AutoUpdater.cpp +++ b/Source/AutoUpdater.cpp @@ -466,17 +466,20 @@ void LatestVersionCheckerAndUpdater::downloadAndInstall (const Asset& asset, con else #endif { - AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, - "Download successful!", - "Please extract the zip file located at: \n" + - targetFile.getFullPathName().quoted() + - "\nto your desired location and then run the updated version from there. " - "You can also overwrite the current installation after quitting the current instance."); + String msgBoxString = "Please extract the zip file located at: \n" + + targetFile.getFullPathName().quoted() + + "\nto your desired location and then run the updated version from there. " + "You can also overwrite the current installation after quitting the current instance."; downloader.reset (new DownloadThread (asset, targetFile, - [this] + [this, msgBoxString] { downloader.reset(); + + AlertWindow::showMessageBoxAsync + (AlertWindow::InfoIcon, + "Download successful!", + msgBoxString); })); } From 8589eadaa6746e87d50776cc210ccbc0afc7458e Mon Sep 17 00:00:00 2001 From: anjaldoshi Date: Thu, 30 Mar 2023 16:49:07 -0700 Subject: [PATCH 4/6] Add support for enabling/disabling automatic update check --- Source/AutoUpdater.cpp | 28 ++++++++++++++++++++-------- Source/AutoUpdater.h | 4 +++- Source/MainWindow.cpp | 7 +++++++ Source/MainWindow.h | 4 ++++ Source/UI/UIComponent.cpp | 2 +- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/Source/AutoUpdater.cpp b/Source/AutoUpdater.cpp index e3c38d1f35..4aa63a81f5 100644 --- a/Source/AutoUpdater.cpp +++ b/Source/AutoUpdater.cpp @@ -24,6 +24,7 @@ #include "AutoUpdater.h" #include "CoreServices.h" +#include "MainWindow.h" #ifdef _WIN32 #include #include @@ -32,6 +33,7 @@ //============================================================================== LatestVersionCheckerAndUpdater::LatestVersionCheckerAndUpdater() : Thread ("VersionChecker") + , mainWindow(nullptr) { } @@ -41,11 +43,12 @@ LatestVersionCheckerAndUpdater::~LatestVersionCheckerAndUpdater() clearSingletonInstance(); } -void LatestVersionCheckerAndUpdater::checkForNewVersion (bool background) +void LatestVersionCheckerAndUpdater::checkForNewVersion (bool background, MainWindow* mw) { if (! isThreadRunning()) { backgroundCheck = background; + mainWindow = mw; startThread (3); } } @@ -179,7 +182,7 @@ void LatestVersionCheckerAndUpdater::run() class UpdateDialog : public Component { public: - UpdateDialog (const String& newVersion, const String& releaseNotes) + UpdateDialog (const String& newVersion, const String& releaseNotes, bool automaticVerCheck) { titleLabel.setText ("Open Ephys GUI version " + newVersion, dontSendNotification); titleLabel.setFont (Font("Fira Sans", "SemiBold", 18.0f)); @@ -203,11 +206,13 @@ class UpdateDialog : public Component addAndMakeVisible (cancelButton); cancelButton.onClick = [this] { - // ProjucerApplication::getApp().setAutomaticVersionCheckingEnabled (! dontAskAgainButton.getToggleState()); - exitModalStateWithResult (-1); + if(dontAskAgainButton.getToggleState()) + exitModalStateWithResult (-1); + else + exitModalStateWithResult(0); }; - dontAskAgainButton.setToggleState (false, dontSendNotification); + dontAskAgainButton.setToggleState (!automaticVerCheck, dontSendNotification); addAndMakeVisible (dontAskAgainButton); #if JUCE_MAC @@ -251,14 +256,15 @@ class UpdateDialog : public Component } static std::unique_ptr launchDialog (const String& newVersionString, - const String& releaseNotes) + const String& releaseNotes, + bool automaticVerCheck) { DialogWindow::LaunchOptions options; options.dialogTitle = "Download Open Ephys GUI version " + newVersionString + "?"; options.resizable = false; - auto* content = new UpdateDialog (newVersionString, releaseNotes); + auto* content = new UpdateDialog (newVersionString, releaseNotes, automaticVerCheck); options.content.set (content, true); std::unique_ptr dialog (options.create()); @@ -319,7 +325,9 @@ void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVe const String& releaseNotes, const Asset& asset) { - dialogWindow = UpdateDialog::launchDialog (newVersionString, releaseNotes); + dialogWindow = UpdateDialog::launchDialog (newVersionString, + releaseNotes, + mainWindow->automaticVersionChecking); if (auto* mm = ModalComponentManager::getInstance()) { @@ -328,6 +336,10 @@ void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVe { if (result == 1) askUserForLocationToDownload (asset); + else if(result == -1) + mainWindow->automaticVersionChecking = false; + else if(result == 0) + mainWindow->automaticVersionChecking = true; dialogWindow.reset(); })); diff --git a/Source/AutoUpdater.h b/Source/AutoUpdater.h index 5b9948496b..cdd9749394 100644 --- a/Source/AutoUpdater.h +++ b/Source/AutoUpdater.h @@ -26,6 +26,7 @@ #include "../JuceLibraryCode/JuceHeader.h" +class MainWindow; class DownloadThread; class LatestVersionCheckerAndUpdater : public DeletedAtShutdown, @@ -42,7 +43,7 @@ class LatestVersionCheckerAndUpdater : public DeletedAtShutdown, const int size; }; - void checkForNewVersion (bool isBackgroundCheck); + void checkForNewVersion (bool isBackgroundCheck, MainWindow* mw); //============================================================================== JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (LatestVersionCheckerAndUpdater) @@ -56,6 +57,7 @@ class LatestVersionCheckerAndUpdater : public DeletedAtShutdown, //============================================================================== bool backgroundCheck = false; + MainWindow* mainWindow; std::unique_ptr downloader; std::unique_ptr dialogWindow; diff --git a/Source/MainWindow.cpp b/Source/MainWindow.cpp index d30943a996..b1b8bf42bf 100644 --- a/Source/MainWindow.cpp +++ b/Source/MainWindow.cpp @@ -25,6 +25,7 @@ #include "Utils/OpenEphysHttpServer.h" #include "UI/UIComponent.h" #include "UI/EditorViewport.h" +#include "AutoUpdater.h" #include @@ -59,6 +60,7 @@ MainWindow::MainWindow(const File& fileToLoad) shouldReloadOnStartup = true; shouldEnableHttpServer = true; openDefaultConfigWindow = false; + automaticVersionChecking = true; // Create ProcessorGraph and AudioComponent, and connect them. // Callbacks will be set by the play button in the control panel @@ -160,6 +162,9 @@ MainWindow::MainWindow(const File& fileToLoad) disableHttpServer(); } + if(automaticVersionChecking) + LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (true, this); + } MainWindow::~MainWindow() @@ -267,6 +272,7 @@ void MainWindow::saveWindowBounds() xml->setAttribute("version", JUCEApplication::getInstance()->getApplicationVersion()); xml->setAttribute("shouldReloadOnStartup", shouldReloadOnStartup); xml->setAttribute("shouldEnableHttpServer", shouldEnableHttpServer); + xml->setAttribute("automaticVersionChecking", automaticVersionChecking); XmlElement* bounds = new XmlElement("BOUNDS"); bounds->setAttribute("x",getScreenX()); @@ -330,6 +336,7 @@ void MainWindow::loadWindowBounds() shouldReloadOnStartup = xml->getBoolAttribute("shouldReloadOnStartup", false); shouldEnableHttpServer = xml->getBoolAttribute("shouldEnableHttpServer", false); + automaticVersionChecking = xml->getBoolAttribute("automaticVersionChecking", true); for (auto* e : xml->getChildIterator()) { diff --git a/Source/MainWindow.h b/Source/MainWindow.h index 4839520267..551b0bf30d 100644 --- a/Source/MainWindow.h +++ b/Source/MainWindow.h @@ -68,8 +68,12 @@ class MainWindow : public DocumentWindow /** Determines whether the ProcessorGraph http server is enabled. */ bool shouldEnableHttpServer; + /** Determines whether the default config selection window needs to open on startup. */ bool openDefaultConfigWindow; + /** Determines whether the Auto Updater needs to run on startup. */ + bool automaticVersionChecking; + /** Ends the process() callbacks and disables all processors.*/ void shutDownGUI(); diff --git a/Source/UI/UIComponent.cpp b/Source/UI/UIComponent.cpp index 306e4e72b6..d7cfa55d0a 100755 --- a/Source/UI/UIComponent.cpp +++ b/Source/UI/UIComponent.cpp @@ -863,7 +863,7 @@ bool UIComponent::perform(const InvocationInfo& info) case checkForUpdates: { - LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (false); + LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (false, mainWindow); break; } From ffa0549885a1e4ee482ebcf447455327de0b6227 Mon Sep 17 00:00:00 2001 From: anjaldoshi Date: Wed, 12 Apr 2023 13:52:41 -0700 Subject: [PATCH 5/6] Download the correct update binaries on Linux & MacOS Auto updater now checks whether to download the .zip binaries or the installer depending on the installation type on Linux & Mac --- Source/AutoUpdater.cpp | 43 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/Source/AutoUpdater.cpp b/Source/AutoUpdater.cpp index 4aa63a81f5..b790a50762 100644 --- a/Source/AutoUpdater.cpp +++ b/Source/AutoUpdater.cpp @@ -156,6 +156,19 @@ void LatestVersionCheckerAndUpdater::run() { requiredFilename = "Install-Open-Ephys-GUI-" + versionString + ".exe"; } +#elif JUCE_LINUX + File exeDir = File::getSpecialLocation(File::SpecialLocationType::currentExecutableFile).getParentDirectory(); + if(exeDir.getFullPathName().contains("/usr/local/bin")) + { + requiredFilename = "open-ephys-gui-" + versionString + ".deb"; + } +#elif JUCE_MAC + File exeDir = File::getSpecialLocation(File::SpecialLocationType::currentApplicationFile).getParentDirectory(); + File globalAppDir = File::getSpecialLocation(File::SpecialLocationType::globalApplicationsDirectory); + if(exeDir.getFullPathName().contains(globalAppDir.getFullPathName())) + { + requiredFilename = "Open_Ephys_GUI_" + versionString + ".dmg"; + } #endif for (auto& asset : parsedAssets) @@ -200,8 +213,8 @@ class UpdateDialog : public Component releaseNotesEditor.setText (releaseNotes); addAndMakeVisible (releaseNotesEditor); - addAndMakeVisible (chooseButton); - chooseButton.onClick = [this] { exitModalStateWithResult (1); }; + addAndMakeVisible (downloadButton); + downloadButton.onClick = [this] { exitModalStateWithResult (1); }; addAndMakeVisible (cancelButton); cancelButton.onClick = [this] @@ -239,7 +252,7 @@ class UpdateDialog : public Component auto buttonBounds = b.removeFromBottom (60); buttonBounds.removeFromBottom (25); - chooseButton.setBounds (buttonBounds.removeFromLeft (buttonBounds.getWidth() / 2).reduced (20, 0)); + downloadButton.setBounds (buttonBounds.removeFromLeft (buttonBounds.getWidth() / 2).reduced (20, 0)); cancelButton.setBounds (buttonBounds.reduced (20, 0)); dontAskAgainButton.setBounds (cancelButton.getBounds().withY (cancelButton.getBottom() + 5).withHeight (20)); @@ -295,7 +308,7 @@ class UpdateDialog : public Component Label titleLabel, contentLabel, releaseNotesLabel; TextEditor releaseNotesEditor; - TextButton chooseButton { "Download" }, cancelButton { "Cancel" }; + TextButton downloadButton { "Download" }, cancelButton { "Cancel" }; ToggleButton dontAskAgainButton { "Don't ask again" }; std::unique_ptr juceIcon; juce::Rectangle juceIconBounds { 10, 10, 64, 64 }; @@ -478,11 +491,25 @@ void LatestVersionCheckerAndUpdater::downloadAndInstall (const Asset& asset, con else #endif { - String msgBoxString = "Please extract the zip file located at: \n" + - targetFile.getFullPathName().quoted() + - "\nto your desired location and then run the updated version from there. " - "You can also overwrite the current installation after quitting the current instance."; + String msgBoxString = String(); + if(targetFile.getFileExtension().equalsIgnoreCase(".zip")) + { + msgBoxString = "Please extract the zip file located at: \n" + + targetFile.getFullPathName().quoted() + + "\nto your desired location and then run the updated version from there. " + "You can also overwrite the current installation after quitting the current instance."; + + } + else + { + msgBoxString = "Please launch the installer file located at: \n" + + targetFile.getFullPathName().quoted() + + "\nafter closing the GUI, " + "and follow the steps to finish updating the GUI."; + } + + downloader.reset (new DownloadThread (asset, targetFile, [this, msgBoxString] { From 8779efa2871aa50b6e64578ef6fd8d4fbd9d8cf1 Mon Sep 17 00:00:00 2001 From: Anjal Doshi Date: Wed, 12 Apr 2023 19:13:36 -0700 Subject: [PATCH 6/6] Check for updates automatically in Release mode only --- CMakeLists.txt | 2 +- Source/AutoUpdater.cpp | 5 ++--- Source/MainWindow.cpp | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f372e0a848..f4e37a0b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ #Open Ephys GUI main build file cmake_minimum_required(VERSION 3.15) -set(GUI_VERSION 0.6.3) +set(GUI_VERSION 0.6.4) string(REGEX MATCHALL "[0-9]+" VERSION_LIST ${GUI_VERSION}) set(GUI_VERSION_HEX "0x") diff --git a/Source/AutoUpdater.cpp b/Source/AutoUpdater.cpp index b790a50762..1fb975aff6 100644 --- a/Source/AutoUpdater.cpp +++ b/Source/AutoUpdater.cpp @@ -503,10 +503,9 @@ void LatestVersionCheckerAndUpdater::downloadAndInstall (const Asset& asset, con } else { - msgBoxString = "Please launch the installer file located at: \n" + + msgBoxString = "Please quit the GUI first, then launch the installer file located at: \n" + targetFile.getFullPathName().quoted() + - "\nafter closing the GUI, " - "and follow the steps to finish updating the GUI."; + "\nand follow the steps to finish updating the GUI."; } diff --git a/Source/MainWindow.cpp b/Source/MainWindow.cpp index b1b8bf42bf..39e87abb52 100644 --- a/Source/MainWindow.cpp +++ b/Source/MainWindow.cpp @@ -162,9 +162,10 @@ MainWindow::MainWindow(const File& fileToLoad) disableHttpServer(); } +#ifdef NDEBUG if(automaticVersionChecking) LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (true, this); - +#endif } MainWindow::~MainWindow()