1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-20 01:14:20 +00:00
JUCE/extras/Introjucer/Source/Application/jucer_AutoUpdater.h
2014-11-15 14:04:26 +00:00

422 lines
14 KiB
C++

/*
==============================================================================
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