|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2013 - Raw Material Software 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.
  ==============================================================================
*/
#ifndef JUCER_AUTOUPDATER_H_INCLUDED
#define JUCER_AUTOUPDATER_H_INCLUDED
//==============================================================================
class LatestVersionChecker  : private Thread,
                              private Timer
{
public:
    LatestVersionChecker()  : Thread ("Updater"),
                              hasAttemptedToReadWebsite (false)
    {
        startTimer (2000);
    }
    ~LatestVersionChecker()
    {
        stopThread (20000);
    }
    static URL getLatestVersionURL()
    {
        return URL ("http://www.juce.com/juce/updates/updatelist.php");
    }
    void checkForNewVersion()
    {
        hasAttemptedToReadWebsite = true;
        {
            const ScopedPointer<InputStream> in (getLatestVersionURL().createInputStream (false));
            if (in == nullptr || threadShouldExit())
                return;  // can't connect: fail silently.
            jsonReply = JSON::parse (in->readEntireStreamAsString());
        }
        if (threadShouldExit())
            return;
        if (jsonReply.isArray() || jsonReply.isObject())
            startTimer (100);
    }
    void processResult (var reply)
    {
        if (reply.isArray())
        {
            askUserAboutNewVersion (VersionInfo (reply[0]));
        }
        else if (reply.isObject())
        {
            // In the far-distant future, this may be contacting a defunct
            // URL, so hopefully the website will contain a helpful message
            // for the user..
            String message = reply.getProperty ("message", var()).toString();
            if (message.isNotEmpty())
            {
                AlertWindow::showMessageBox (AlertWindow::WarningIcon,
                                             TRANS("JUCE Updater"),
                                             message);
            }
        }
    }
    struct VersionInfo
    {
        VersionInfo (var v)
        {
            version = v.getProperty ("version", var()).toString().trim();
            url = v.getProperty (
                                #if JUCE_MAC
                                 "url_osx",
                                #elif JUCE_WINDOWS
                                 "url_win",
                                 #elif JUCE_LINUX
                                 "url_linux",
                                #endif
                                 var()).toString();
        }
        bool isDifferentVersionToCurrent() const
        {
            return version != JUCE_STRINGIFY(JUCE_MAJOR_VERSION)
                               "." JUCE_STRINGIFY(JUCE_MINOR_VERSION)
                               "." JUCE_STRINGIFY(JUCE_BUILDNUMBER)
                    && version.containsChar ('.')
                    && version.length() > 2;
        }
        String version;
        URL url;
    };
    void askUserAboutNewVersion (const VersionInfo& info)
    {
        if (info.isDifferentVersionToCurrent())
        {
            File appParentFolder (File::getSpecialLocation (File::currentApplicationFile).getParentDirectory());
            if (isZipFolder (appParentFolder))
            {
                int result = AlertWindow::showYesNoCancelBox (AlertWindow::InfoIcon,
                                                  TRANS("Download JUCE version 123?").replace ("123", info.version),
                                                  TRANS("A new version of JUCE is available - would you like to overwrite the folder:\n\n"
                                                        "xfldrx\n\n"
                                                        " ..with the latest version from juce.com?\n\n"
                                                        "(Please note that this will overwrite everything in that folder!)")
                                                    .replace ("xfldrx", appParentFolder.getFullPathName()),
                                                  TRANS("Overwrite"),
                                                  TRANS("Choose another folder..."),
                                                  TRANS("Cancel"));
                if (result == 1)
                    DownloadNewVersionThread::performDownload (info.url, appParentFolder);
                if (result == 2)
                    askUserForLocationToDownload (info);
            }
            else
            {
                if (AlertWindow::showOkCancelBox (AlertWindow::InfoIcon,
                                                  TRANS("Download JUCE version 123?").replace ("123", info.version),
                                                  TRANS("A new version of JUCE is available - would you like to download it?")))
                    askUserForLocationToDownload (info);
            }
        }
    }
    void askUserForLocationToDownload (const VersionInfo& info)
    {
        File targetFolder (findDefaultModulesFolder());
        if (isJuceModulesFolder (targetFolder))
            targetFolder = targetFolder.getParentDirectory();
        FileChooser chooser (TRANS("Please select the location into which you'd like to install the new version"),
                             targetFolder);
        if (chooser.browseForDirectory())
        {
            targetFolder = chooser.getResult();
            if (isJuceModulesFolder (targetFolder))
                targetFolder = targetFolder.getParentDirectory();
            if (targetFolder.getChildFile ("JUCE").isDirectory())
                targetFolder = targetFolder.getChildFile ("JUCE");
            if (targetFolder.getChildFile (".git").isDirectory())
            {
                AlertWindow::showMessageBox (AlertWindow::WarningIcon,
                                             TRANS ("Downloading new JUCE version"),
                                             TRANS ("This folder is a GIT repository!\n\n"
                                                    "You should use a \"git pull\" to update it to the latest version. "
                                                    "Or to use the Introjucer to get an update, you should select an empty "
                                                    "folder into which you'd like to download the new code."));
                return;
            }
            if (isJuceFolder (targetFolder))
            {
                if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
                                                    TRANS("Overwrite existing JUCE folder?"),
                                                    TRANS("Do you want to overwrite the folder:\n\n"
                                                          "xfldrx\n\n"
                                                          " ..with the latest version from juce.com?\n\n"
                                                          "(Please note that this will overwrite everything in that folder!)")
                                                        .replace ("xfldrx", targetFolder.getFullPathName())))
                {
                    return;
                }
            }
            else
            {
                targetFolder = targetFolder.getChildFile ("JUCE").getNonexistentSibling();
            }
            DownloadNewVersionThread::performDownload (info.url, targetFolder);
        }
    }
    static bool isZipFolder (const File& f)
    {
        return f.getChildFile ("modules").isDirectory()
            && f.getChildFile ("extras").isDirectory()
            && f.getChildFile ("examples").isDirectory()
            && ! f.getChildFile (".git").isDirectory();
    }
private:
    void timerCallback() override
    {
        stopTimer();
        if (hasAttemptedToReadWebsite)
            processResult (jsonReply);
        else
            startThread (3);
    }
    void run() override
    {
        checkForNewVersion();
    }
    var jsonReply;
    bool hasAttemptedToReadWebsite;
    URL newVersionToDownload;
    //==============================================================================
    class DownloadNewVersionThread   : public ThreadWithProgressWindow
    {
    public:
        DownloadNewVersionThread (URL u, File target)
            : ThreadWithProgressWindow ("Downloading New Version", true, true),
              result (Result::ok()),
              url (u), targetFolder (target)
        {
        }
        static void performDownload (URL u, File targetFolder)
        {
            DownloadNewVersionThread d (u, 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())
                result = unzip (zipData);
        }
        Result download (MemoryBlock& dest)
        {
            setStatusMessage ("Downloading...");
            const ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String::empty, 10000));
            if (in != nullptr)
            {
                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));
        }
        Result unzip (const MemoryBlock& data)
        {
            setStatusMessage ("Installing...");
            File unzipTarget;
            bool isUsingTempFolder = false;
            {
                MemoryInputStream input (data, false);
                ZipFile zip (input);
                if (zip.getNumEntries() == 0)
                    return Result::fail ("The downloaded file wasn't a valid JUCE file!");
                unzipTarget = targetFolder;
                if (unzipTarget.exists())
                {
                    isUsingTempFolder = true;
                    unzipTarget = targetFolder.getNonexistentSibling();
                    if (! unzipTarget.createDirectory())
                        return Result::fail ("Couldn't create a folder to unzip the new version!");
                }
                Result r (zip.uncompressTo (unzipTarget));
                if (r.failed())
                {
                    if (isUsingTempFolder)
                        unzipTarget.deleteRecursively();
                    return r;
                }
            }
            if (isUsingTempFolder)
            {
                File oldFolder (targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling());
                if (! targetFolder.moveFileTo (oldFolder))
                {
                    unzipTarget.deleteRecursively();
                    return Result::fail ("Could not remove the existing folder!");
                }
                if (! unzipTarget.moveFileTo (targetFolder))
                {
                    unzipTarget.deleteRecursively();
                    return Result::fail ("Could not overwrite the existing folder!");
                }
            }
            return Result::ok();
        }
        Result result;
        URL url;
        File targetFolder;
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadNewVersionThread)
    };
    struct RelaunchTimer  : private Timer
    {
        RelaunchTimer (const File& f)  : parentFolder (f)
        {
            startTimer (1500);
        }
        void timerCallback() override
        {
            stopTimer();
            File app = parentFolder.getChildFile (
                        #if JUCE_MAC
                         "Introjucer.app");
                        #elif JUCE_WINDOWS
                         "Introjucer.exe");
                         #elif JUCE_LINUX
                         "Introjucer");
                        #endif
            JUCEApplication::quit();
            if (app.exists())
            {
                app.setExecutePermission (true);
               #if JUCE_MAC
                app.getChildFile("Contents")
                   .getChildFile("MacOS")
                   .getChildFile("Introjucer").setExecutePermission (true);
               #endif
                app.startAsProcess();
            }
            delete this;
        }
        File parentFolder;
    };
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LatestVersionChecker)
};
#endif  // JUCER_AUTOUPDATER_H_INCLUDED
 |