/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online 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.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "../jucer_Headers.h" #include "jucer_JuceUpdater.h" //============================================================================== JuceUpdater::JuceUpdater() : filenameComp ("Juce Folder", StoredSettings::getInstance()->getLastKnownJuceFolder(), true, true, false, "*", String::empty, "Select your Juce folder"), checkNowButton ("Check Online for Available Updates...", "Contacts the website to see if this version is up-to-date") { addAndMakeVisible (&label); addAndMakeVisible (&filenameComp); addAndMakeVisible (&checkNowButton); addAndMakeVisible (¤tVersionLabel); checkNowButton.addListener (this); filenameComp.addListener (this); currentVersionLabel.setFont (Font (14.0f, Font::italic)); label.setFont (Font (12.0f)); label.setText ("Destination folder:", false); addAndMakeVisible (&availableVersionsList); availableVersionsList.setModel (this); setSize (600, 300); } JuceUpdater::~JuceUpdater() { checkNowButton.removeListener (this); filenameComp.removeListener (this); } void JuceUpdater::show (Component* mainWindow) { JuceUpdater updater; DialogWindow::showModalDialog ("Juce Update...", &updater, mainWindow, Colours::lightgrey, true, false, false); } void JuceUpdater::resized() { filenameComp.setBounds (20, 40, getWidth() - 40, 22); label.setBounds (filenameComp.getX(), filenameComp.getY() - 18, filenameComp.getWidth(), 18); currentVersionLabel.setBounds (filenameComp.getX(), filenameComp.getBottom(), filenameComp.getWidth(), 25); checkNowButton.changeWidthToFitText (20); checkNowButton.setCentrePosition (getWidth() / 2, filenameComp.getBottom() + 40); availableVersionsList.setBounds (filenameComp.getX(), checkNowButton.getBottom() + 20, filenameComp.getWidth(), getHeight() - (checkNowButton.getBottom() + 20)); } void JuceUpdater::paint (Graphics& g) { g.fillAll (Colours::white); } String findVersionNum (const String& file, const String& token) { return file.fromFirstOccurrenceOf (token, false, false) .upToFirstOccurrenceOf ("\n", false, false) .trim(); } String JuceUpdater::getCurrentVersion() { const String header (filenameComp.getCurrentFile() .getChildFile ("src/core/juce_StandardHeader.h").loadFileAsString()); const String v1 (findVersionNum (header, "JUCE_MAJOR_VERSION")); const String v2 (findVersionNum (header, "JUCE_MINOR_VERSION")); const String v3 (findVersionNum (header, "JUCE_BUILDNUMBER")); if ((v1 + v2 + v3).isEmpty()) return String::empty; return v1 + "." + v2 + "." + v3; } XmlElement* JuceUpdater::downloadVersionList() { return URL ("http://www.rawmaterialsoftware.com/juce/downloads/juce_versions.php").readEntireXmlStream(); } void JuceUpdater::updateVersions (const XmlElement& xml) { availableVersions.clear(); forEachXmlChildElementWithTagName (xml, v, "VERSION") { VersionInfo* vi = new VersionInfo(); vi->url = URL (v->getStringAttribute ("url")); vi->desc = v->getStringAttribute ("desc"); vi->version = v->getStringAttribute ("version"); vi->date = v->getStringAttribute ("date"); availableVersions.add (vi); } availableVersionsList.updateContent(); } void JuceUpdater::buttonClicked (Button*) { ScopedPointer xml (downloadVersionList()); if (xml == nullptr || xml->hasTagName ("html")) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Connection Problems...", "Couldn't connect to the Raw Material Software website!"); return; } if (! xml->hasTagName ("JUCEVERSIONS")) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Update Problems...", "This version of the Introjucer may be too old to receive automatic updates!\n\n" "Please visit www.rawmaterialsoftware.com and get the latest version manually!"); return; } const String currentVersion (getCurrentVersion()); OwnedArray versions; updateVersions (*xml); } //============================================================================== class NewVersionDownloader : public ThreadWithProgressWindow { public: NewVersionDownloader (const String& title, const URL& url_, const File& target_) : ThreadWithProgressWindow (title, true, true), url (url_), target (target_) { } void run() { setStatusMessage ("Contacting website..."); ScopedPointer input (url.createInputStream (false)); if (input == nullptr) { error = "Couldn't connect to the website..."; return; } if (! target.deleteFile()) { error = "Couldn't delete the destination file..."; return; } ScopedPointer output (target.createOutputStream (32768)); if (output == nullptr) { error = "Couldn't write to the destination file..."; return; } setStatusMessage ("Downloading..."); int totalBytes = (int) input->getTotalLength(); int bytesSoFar = 0; while (! (input->isExhausted() || threadShouldExit())) { HeapBlock buffer (8192); const int num = input->read (buffer, 8192); if (num == 0) break; output->write (buffer, num); bytesSoFar += num; setProgress (totalBytes > 0 ? bytesSoFar / (double) totalBytes : -1.0); } } String error; private: URL url; File target; }; //============================================================================== class Unzipper : public ThreadWithProgressWindow { public: Unzipper (ZipFile& zipFile_, const File& targetDir_) : ThreadWithProgressWindow ("Unzipping...", true, true), worked (true), zipFile (zipFile_), targetDir (targetDir_) { } void run() { for (int i = 0; i < zipFile.getNumEntries(); ++i) { if (threadShouldExit()) break; const ZipFile::ZipEntry* e = zipFile.getEntry (i); setStatusMessage ("Unzipping " + e->filename + "..."); setProgress (i / (double) zipFile.getNumEntries()); worked = zipFile.uncompressEntry (i, targetDir, true) && worked; } } bool worked; private: ZipFile& zipFile; File targetDir; }; //============================================================================== void JuceUpdater::applyVersion (VersionInfo* version) { File destDir (filenameComp.getCurrentFile()); const bool destDirExisted = destDir.isDirectory(); if (destDirExisted && destDir.getNumberOfChildFiles (File::findFilesAndDirectories, "*") > 0) { int r = AlertWindow::showYesNoCancelBox (AlertWindow::WarningIcon, "Folder already exists", "The folder " + destDir.getFullPathName() + "\nalready contains some files...\n\n" "Do you want to delete everything in the folder and replace it entirely, or just merge the new files into the existing folder?", "Delete and replace entire folder", "Add and overwrite existing files", "Cancel"); if (r == 0) return; if (r == 1) { if (! destDir.deleteRecursively()) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Problems...", "Couldn't delete the existing folder!"); return; } } } if (! (destDir.isDirectory() || destDir.createDirectory())) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Problems...", "Couldn't create that target folder.."); return; } File zipFile (destDir.getNonexistentChildFile ("juce_download", ".tar.gz", false)); bool worked = false; { NewVersionDownloader downloader ("Downloading Version " + version->version + "...", version->url, zipFile); worked = downloader.runThread(); } if (worked) { ZipFile zip (zipFile); Unzipper unzipper (zip, destDir); worked = unzipper.runThread() && unzipper.worked; } zipFile.deleteFile(); if ((! destDirExisted) && (destDir.getNumberOfChildFiles (File::findFilesAndDirectories, "*") == 0 || ! worked)) destDir.deleteRecursively(); filenameComponentChanged (&filenameComp); } void JuceUpdater::filenameComponentChanged (FilenameComponent*) { const String version (getCurrentVersion()); if (version.isEmpty()) currentVersionLabel.setText ("(Not a Juce folder)", false); else currentVersionLabel.setText ("(Current version in this folder: " + version + ")", false); } //============================================================================== int JuceUpdater::getNumRows() { return availableVersions.size(); } void JuceUpdater::paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) { if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId)); } Component* JuceUpdater::refreshComponentForRow (int rowNumber, bool isRowSelected, Component* existingComponentToUpdate) { class UpdateListComponent : public Component, public ButtonListener { public: UpdateListComponent (JuceUpdater& updater_) : updater (updater_), version (nullptr), applyButton ("Install this version...") { addAndMakeVisible (&applyButton); applyButton.addListener (this); setInterceptsMouseClicks (false, true); } ~UpdateListComponent() { applyButton.removeListener (this); } void setVersion (VersionInfo* v) { if (version != v) { version = v; repaint(); resized(); } } void paint (Graphics& g) { if (version != nullptr) { g.setColour (Colours::green.withAlpha (0.12f)); g.fillRect (0, 1, getWidth(), getHeight() - 2); g.setColour (Colours::black); g.setFont (getHeight() * 0.7f); String s; s << "Version " << version->version << " - " << version->desc << " - " << version->date; g.drawText (s, 4, 0, applyButton.getX() - 4, getHeight(), Justification::centredLeft, true); } } void resized() { applyButton.changeWidthToFitText (getHeight() - 4); applyButton.setTopRightPosition (getWidth(), 2); applyButton.setVisible (version != nullptr); } void buttonClicked (Button*) { updater.applyVersion (version); } private: JuceUpdater& updater; VersionInfo* version; TextButton applyButton; }; UpdateListComponent* c = dynamic_cast (existingComponentToUpdate); if (c == nullptr) c = new UpdateListComponent (*this); c->setVersion (availableVersions [rowNumber]); return c; }