/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE 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. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ #include "../jucer_Headers.h" #include "jucer_Application.h" #include "jucer_AutoUpdater.h" LatestVersionChecker::JuceVersionTriple::JuceVersionTriple() : major ((ProjectInfo::versionNumber & 0xff0000) >> 16), minor ((ProjectInfo::versionNumber & 0x00ff00) >> 8), build ((ProjectInfo::versionNumber & 0x0000ff) >> 0) {} LatestVersionChecker::JuceVersionTriple::JuceVersionTriple (int juceVersionNumber) : major ((juceVersionNumber & 0xff0000) >> 16), minor ((juceVersionNumber & 0x00ff00) >> 8), build ((juceVersionNumber & 0x0000ff) >> 0) {} LatestVersionChecker::JuceVersionTriple::JuceVersionTriple (int majorInt, int minorInt, int buildNumber) : major (majorInt), minor (minorInt), build (buildNumber) {} bool LatestVersionChecker::JuceVersionTriple::fromString (const String& versionString, LatestVersionChecker::JuceVersionTriple& result) { StringArray tokenizedString = StringArray::fromTokens (versionString, ".", StringRef()); if (tokenizedString.size() != 3) return false; result.major = tokenizedString [0].getIntValue(); result.minor = tokenizedString [1].getIntValue(); result.build = tokenizedString [2].getIntValue(); return true; } String LatestVersionChecker::JuceVersionTriple::toString() const { String retval; retval << major << '.' << minor << '.' << build; return retval; } bool LatestVersionChecker::JuceVersionTriple::operator> (const LatestVersionChecker::JuceVersionTriple& b) const noexcept { if (major == b.major) { if (minor == b.minor) return build > b.build; return minor > b.minor; } return major > b.major; } //============================================================================== struct RelaunchTimer : private Timer { RelaunchTimer (const File& f) : parentFolder (f) { startTimer (1500); } void timerCallback() override { stopTimer(); File app = parentFolder.getChildFile ( #if JUCE_MAC "Projucer.app"); #elif JUCE_WINDOWS "Projucer.exe"); #elif JUCE_LINUX "Projucer"); #endif JUCEApplication::quit(); if (app.exists()) { app.setExecutePermission (true); #if JUCE_MAC app.getChildFile ("Contents") .getChildFile ("MacOS") .getChildFile ("Projucer").setExecutePermission (true); #endif app.startAsProcess(); } delete this; } File parentFolder; }; //============================================================================== class DownloadNewVersionThread : public ThreadWithProgressWindow { public: DownloadNewVersionThread (LatestVersionChecker& versionChecker,URL u, const String& extraHeaders, File target) : ThreadWithProgressWindow ("Downloading New Version", true, true), owner (versionChecker), result (Result::ok()), url (u), headers (extraHeaders), targetFolder (target) { } static void performDownload (LatestVersionChecker& versionChecker, URL u, const String& extraHeaders, File targetFolder) { DownloadNewVersionThread d (versionChecker, u, extraHeaders, targetFolder); if (d.runThread()) { if (d.result.failed()) { AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Installation Failed", d.result.getErrorMessage()); } else { new RelaunchTimer (targetFolder); } } } void run() override { setProgress (-1.0); MemoryBlock zipData; result = download (zipData); if (result.wasOk() && ! threadShouldExit()) { setStatusMessage ("Installing..."); result = owner.performUpdate (zipData, targetFolder); } } Result download (MemoryBlock& dest) { setStatusMessage ("Downloading..."); int statusCode = 302; const int maxRedirects = 5; // we need to do the redirecting manually due to inconsistencies on the way headers are handled on redirects ScopedPointer in; for (int redirect = 0; redirect < maxRedirects; ++redirect) { StringPairArray responseHeaders; in = url.createInputStream (false, nullptr, nullptr, headers, 10000, &responseHeaders, &statusCode, 0); if (in == nullptr || statusCode != 302) break; String redirectPath = responseHeaders ["Location"]; if (redirectPath.isEmpty()) break; url = owner.getLatestVersionURL (headers, redirectPath); } if (in != nullptr && statusCode == 200) { int64 total = 0; MemoryOutputStream mo (dest, true); for (;;) { if (threadShouldExit()) return Result::fail ("cancel"); int64 written = mo.writeFromInputStream (*in, 8192); if (written == 0) break; total += written; setStatusMessage (String (TRANS ("Downloading... (123)")) .replace ("123", File::descriptionOfSizeInBytes (total))); } return Result::ok(); } return Result::fail ("Failed to download from: " + url.toString (false)); } LatestVersionChecker& owner; Result result; URL url; String headers; File targetFolder; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadNewVersionThread) }; //============================================================================== class UpdateUserDialog : public Component, public ButtonListener { public: UpdateUserDialog (const LatestVersionChecker::JuceVersionTriple& version, const String& productName, const String& releaseNotes, const char* overwriteFolderPath) : hasOverwriteButton (overwriteFolderPath != nullptr) { addAndMakeVisible (titleLabel = new Label ("Title Label", TRANS ("Download \"123\" version 456?").replace ("123", productName) .replace ("456", version.toString()))); titleLabel->setFont (Font (15.00f, Font::bold)); titleLabel->setJustificationType (Justification::centredLeft); titleLabel->setEditable (false, false, false); addAndMakeVisible (contentLabel = new Label ("Content Label", TRANS ("A new version of \"123\" is available - would you like to download it?") .replace ("123", productName))); contentLabel->setFont (Font (15.00f, Font::plain)); contentLabel->setJustificationType (Justification::topLeft); contentLabel->setEditable (false, false, false); addAndMakeVisible (okButton = new TextButton ("OK Button")); okButton->setButtonText (TRANS(hasOverwriteButton ? "Choose Another Folder..." : "OK")); okButton->addListener (this); addAndMakeVisible (cancelButton = new TextButton ("Cancel Button")); cancelButton->setButtonText (TRANS("Cancel")); cancelButton->addListener (this); cancelButton->setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId)); addAndMakeVisible (changeLogLabel = new Label ("Change Log Label", TRANS("Release Notes:"))); changeLogLabel->setFont (Font (15.00f, Font::plain)); changeLogLabel->setJustificationType (Justification::topLeft); changeLogLabel->setEditable (false, false, false); addAndMakeVisible (changeLog = new TextEditor ("Change Log")); changeLog->setMultiLine (true); changeLog->setReturnKeyStartsNewLine (true); changeLog->setReadOnly (true); changeLog->setScrollbarsShown (true); changeLog->setCaretVisible (false); changeLog->setPopupMenuEnabled (false); changeLog->setText (releaseNotes); if (hasOverwriteButton) { addAndMakeVisible (overwriteLabel = new Label ("Overwrite Label", TRANS("Updating will overwrite everything in the following folder:"))); overwriteLabel->setFont (Font (15.00f, Font::plain)); overwriteLabel->setJustificationType (Justification::topLeft); overwriteLabel->setEditable (false, false, false); addAndMakeVisible (overwritePath = new Label ("Overwrite Path", overwriteFolderPath)); overwritePath->setFont (Font (15.00f, Font::bold)); overwritePath->setJustificationType (Justification::topLeft); overwritePath->setEditable (false, false, false); addAndMakeVisible (overwriteButton = new TextButton ("Overwrite Button")); overwriteButton->setButtonText (TRANS("Overwrite")); overwriteButton->addListener (this); } juceIcon = Drawable::createFromImageData (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize); setSize (518, overwritePath ? 345 : 269); } ~UpdateUserDialog() { titleLabel = nullptr; contentLabel = nullptr; okButton = nullptr; cancelButton = nullptr; changeLogLabel = nullptr; changeLog = nullptr; overwriteLabel = nullptr; overwritePath = nullptr; overwriteButton = nullptr; juceIcon = nullptr; } void paint (Graphics& g) override { g.fillAll (findColour (backgroundColourId)); g.setColour (findColour (defaultTextColourId)); if (juceIcon != nullptr) juceIcon->drawWithin (g, Rectangle (20, 17, 64, 64), RectanglePlacement::stretchToFit, 1.000f); } void resized() override { titleLabel->setBounds (88, 10, 397, 24); contentLabel->setBounds (88, 40, 397, 51); changeLogLabel->setBounds (22, 92, 341, 24); changeLog->setBounds (24, 112, 476, 102); if (hasOverwriteButton) { okButton->setBounds (getWidth() - 24 - 174, getHeight() - 37, 174, 28); overwriteButton->setBounds ((getWidth() - 24 - 174) + -14 - 86, getHeight() - 37, 86, 28); cancelButton->setBounds (24, getHeight() - 37, 70, 28); overwriteLabel->setBounds (24, 238, 472, 16); overwritePath->setBounds (24, 262, 472, 40); } else { okButton->setBounds (getWidth() - 24 - 47, getHeight() - 37, 47, 28); cancelButton->setBounds ((getWidth() - 24 - 47) + -14 - 70, getHeight() - 37, 70, 28); } } void buttonClicked (Button* clickedButton) override { if (DialogWindow* parentDialog = findParentComponentOfClass()) { if (clickedButton == overwriteButton) parentDialog->exitModalState (1); else if (clickedButton == okButton) parentDialog->exitModalState (2); else if (clickedButton == cancelButton) parentDialog->exitModalState (-1); } else jassertfalse; } static DialogWindow* launch (const LatestVersionChecker::JuceVersionTriple& version, const String& productName, const String& releaseNotes, const char* overwritePath = nullptr) { OptionalScopedPointer userDialog (new UpdateUserDialog (version, productName, releaseNotes, overwritePath), true); DialogWindow::LaunchOptions lo; lo.dialogTitle = TRANS ("Download \"123\" version 456?").replace ("456", version.toString()) .replace ("123", productName); lo.dialogBackgroundColour = userDialog->findColour (backgroundColourId); lo.content = userDialog; lo.componentToCentreAround = nullptr; lo.escapeKeyTriggersCloseButton = true; lo.useNativeTitleBar = true; lo.resizable = false; lo.useBottomRightCornerResizer = false; return lo.launchAsync(); } private: bool hasOverwriteButton; ScopedPointer