The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

790 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "jucer_Application.h"
  18. #include "jucer_AutoUpdater.h"
  19. LatestVersionChecker::JuceVersionTriple::JuceVersionTriple()
  20. : major (JUCE_MAJOR_VERSION),
  21. minor (JUCE_MINOR_VERSION),
  22. build (JUCE_BUILDNUMBER)
  23. {}
  24. LatestVersionChecker::JuceVersionTriple::JuceVersionTriple (int majorInt, int minorInt, int buildNumber)
  25. : major (majorInt),
  26. minor (minorInt),
  27. build (buildNumber)
  28. {}
  29. bool LatestVersionChecker::JuceVersionTriple::fromString (const String& versionString,
  30. LatestVersionChecker::JuceVersionTriple& result)
  31. {
  32. StringArray tokenizedString = StringArray::fromTokens (versionString, ".", StringRef());
  33. if (tokenizedString.size() != 3)
  34. return false;
  35. result.major = tokenizedString [0].getIntValue();
  36. result.minor = tokenizedString [1].getIntValue();
  37. result.build = tokenizedString [2].getIntValue();
  38. return true;
  39. }
  40. String LatestVersionChecker::JuceVersionTriple::toString() const
  41. {
  42. String retval;
  43. retval << major << '.' << minor << '.' << build;
  44. return retval;
  45. }
  46. bool LatestVersionChecker::JuceVersionTriple::operator> (const LatestVersionChecker::JuceVersionTriple& b) const noexcept
  47. {
  48. if (major == b.major)
  49. {
  50. if (minor == b.minor)
  51. return build > b.build;
  52. return minor > b.minor;
  53. }
  54. return major > b.major;
  55. }
  56. //==============================================================================
  57. struct RelaunchTimer : private Timer
  58. {
  59. RelaunchTimer (const File& f) : parentFolder (f)
  60. {
  61. startTimer (1500);
  62. }
  63. void timerCallback() override
  64. {
  65. stopTimer();
  66. File app = parentFolder.getChildFile (
  67. #if JUCE_MAC
  68. "Introjucer.app");
  69. #elif JUCE_WINDOWS
  70. "Introjucer.exe");
  71. #elif JUCE_LINUX
  72. "Introjucer");
  73. #endif
  74. JUCEApplication::quit();
  75. if (app.exists())
  76. {
  77. app.setExecutePermission (true);
  78. #if JUCE_MAC
  79. app.getChildFile ("Contents")
  80. .getChildFile ("MacOS")
  81. .getChildFile ("Introjucer").setExecutePermission (true);
  82. #endif
  83. app.startAsProcess();
  84. }
  85. delete this;
  86. }
  87. File parentFolder;
  88. };
  89. //==============================================================================
  90. class DownloadNewVersionThread : public ThreadWithProgressWindow
  91. {
  92. public:
  93. DownloadNewVersionThread (URL u, const String& extraHeaders, File target)
  94. : ThreadWithProgressWindow ("Downloading New Version", true, true),
  95. result (Result::ok()),
  96. url (u), headers (extraHeaders), targetFolder (target)
  97. {
  98. }
  99. static void performDownload (URL u, const String& extraHeaders, File targetFolder)
  100. {
  101. DownloadNewVersionThread d (u, extraHeaders, targetFolder);
  102. if (d.runThread())
  103. {
  104. if (d.result.failed())
  105. {
  106. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  107. "Installation Failed",
  108. d.result.getErrorMessage());
  109. }
  110. else
  111. {
  112. new RelaunchTimer (targetFolder);
  113. }
  114. }
  115. }
  116. void run() override
  117. {
  118. setProgress (-1.0);
  119. MemoryBlock zipData;
  120. result = download (zipData);
  121. if (result.wasOk() && ! threadShouldExit())
  122. result = unzip (zipData);
  123. }
  124. Result download (MemoryBlock& dest)
  125. {
  126. setStatusMessage ("Downloading...");
  127. int statusCode = 302;
  128. const int maxRedirects = 5;
  129. // we need to do the redirecting manually due to inconsistencies on the way headers are handled on redirects
  130. ScopedPointer<InputStream> in;
  131. for (int redirect = 0; redirect < maxRedirects; ++redirect)
  132. {
  133. StringPairArray responseHeaders;
  134. in = url.createInputStream (false, nullptr, nullptr, headers, 10000, &responseHeaders, &statusCode, 0);
  135. if (in == nullptr || statusCode != 302)
  136. break;
  137. String redirectPath = responseHeaders ["Location"];
  138. if (redirectPath.isEmpty())
  139. break;
  140. url = LatestVersionChecker::getLatestVersionURL (headers, redirectPath);
  141. }
  142. if (in != nullptr && statusCode == 200)
  143. {
  144. int64 total = 0;
  145. MemoryOutputStream mo (dest, true);
  146. for (;;)
  147. {
  148. if (threadShouldExit())
  149. return Result::fail ("cancel");
  150. int64 written = mo.writeFromInputStream (*in, 8192);
  151. if (written == 0)
  152. break;
  153. total += written;
  154. setStatusMessage (String (TRANS ("Downloading... (123)"))
  155. .replace ("123", File::descriptionOfSizeInBytes (total)));
  156. }
  157. return Result::ok();
  158. }
  159. return Result::fail ("Failed to download from: " + url.toString (false));
  160. }
  161. Result unzip (const MemoryBlock& data)
  162. {
  163. setStatusMessage ("Installing...");
  164. File unzipTarget;
  165. bool isUsingTempFolder = false;
  166. {
  167. MemoryInputStream input (data, false);
  168. ZipFile zip (input);
  169. if (zip.getNumEntries() == 0)
  170. return Result::fail ("The downloaded file wasn't a valid JUCE file!");
  171. unzipTarget = targetFolder;
  172. if (unzipTarget.exists())
  173. {
  174. isUsingTempFolder = true;
  175. unzipTarget = targetFolder.getNonexistentSibling();
  176. if (! unzipTarget.createDirectory())
  177. return Result::fail ("Couldn't create a folder to unzip the new version!");
  178. }
  179. Result r (zip.uncompressTo (unzipTarget));
  180. if (r.failed())
  181. {
  182. if (isUsingTempFolder)
  183. unzipTarget.deleteRecursively();
  184. return r;
  185. }
  186. }
  187. if (isUsingTempFolder)
  188. {
  189. File oldFolder (targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old")
  190. .getNonexistentSibling());
  191. if (! targetFolder.moveFileTo (oldFolder))
  192. {
  193. unzipTarget.deleteRecursively();
  194. return Result::fail ("Could not remove the existing folder!");
  195. }
  196. if (! unzipTarget.moveFileTo (targetFolder))
  197. {
  198. unzipTarget.deleteRecursively();
  199. return Result::fail ("Could not overwrite the existing folder!");
  200. }
  201. }
  202. return Result::ok();
  203. }
  204. Result result;
  205. URL url;
  206. String headers;
  207. File targetFolder;
  208. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadNewVersionThread)
  209. };
  210. //==============================================================================
  211. class UpdateUserDialog : public Component,
  212. public ButtonListener
  213. {
  214. public:
  215. UpdateUserDialog (const LatestVersionChecker::JuceVersionTriple& version,
  216. const String& releaseNotes,
  217. const char* overwriteFolderPath)
  218. : hasOverwriteButton (overwriteFolderPath != nullptr)
  219. {
  220. addAndMakeVisible (titleLabel = new Label ("Title Label",
  221. TRANS ("Download JUCE version 123?").
  222. replace ("123", version.toString())));
  223. titleLabel->setFont (Font (15.00f, Font::bold));
  224. titleLabel->setJustificationType (Justification::centredLeft);
  225. titleLabel->setEditable (false, false, false);
  226. titleLabel->setColour (TextEditor::textColourId, Colours::black);
  227. titleLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  228. addAndMakeVisible (contentLabel = new Label ("Content Label",
  229. TRANS("A new version of JUCE is available - would you like to download it?")));
  230. contentLabel->setFont (Font (15.00f, Font::plain));
  231. contentLabel->setJustificationType (Justification::topLeft);
  232. contentLabel->setEditable (false, false, false);
  233. contentLabel->setColour (TextEditor::textColourId, Colours::black);
  234. contentLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  235. addAndMakeVisible (okButton = new TextButton ("OK Button"));
  236. okButton->setButtonText (TRANS(hasOverwriteButton ? "Choose Another Folder..." : "OK"));
  237. okButton->addListener (this);
  238. addAndMakeVisible (cancelButton = new TextButton ("Cancel Button"));
  239. cancelButton->setButtonText (TRANS("Cancel"));
  240. cancelButton->addListener (this);
  241. addAndMakeVisible (changeLogLabel = new Label ("Change Log Label",
  242. TRANS("Release Notes:")));
  243. changeLogLabel->setFont (Font (15.00f, Font::plain));
  244. changeLogLabel->setJustificationType (Justification::topLeft);
  245. changeLogLabel->setEditable (false, false, false);
  246. changeLogLabel->setColour (TextEditor::textColourId, Colours::black);
  247. changeLogLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  248. addAndMakeVisible (changeLog = new TextEditor ("Change Log"));
  249. changeLog->setMultiLine (true);
  250. changeLog->setReturnKeyStartsNewLine (true);
  251. changeLog->setReadOnly (true);
  252. changeLog->setScrollbarsShown (true);
  253. changeLog->setCaretVisible (false);
  254. changeLog->setPopupMenuEnabled (false);
  255. changeLog->setText (releaseNotes);
  256. if (hasOverwriteButton)
  257. {
  258. addAndMakeVisible (overwriteLabel = new Label ("Overwrite Label",
  259. TRANS("Updating will overwrite everything in the following folder:")));
  260. overwriteLabel->setFont (Font (15.00f, Font::plain));
  261. overwriteLabel->setJustificationType (Justification::topLeft);
  262. overwriteLabel->setEditable (false, false, false);
  263. overwriteLabel->setColour (TextEditor::textColourId, Colours::black);
  264. overwriteLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  265. addAndMakeVisible (overwritePath = new Label ("Overwrite Path", overwriteFolderPath));
  266. overwritePath->setFont (Font (15.00f, Font::bold));
  267. overwritePath->setJustificationType (Justification::topLeft);
  268. overwritePath->setEditable (false, false, false);
  269. overwritePath->setColour (TextEditor::textColourId, Colours::black);
  270. overwritePath->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  271. addAndMakeVisible (overwriteButton = new TextButton ("Overwrite Button"));
  272. overwriteButton->setButtonText (TRANS("Overwrite"));
  273. overwriteButton->addListener (this);
  274. }
  275. juceIcon = Drawable::createFromImageData (BinaryData::juce_icon_png,
  276. BinaryData::juce_icon_pngSize);
  277. setSize (518, overwritePath ? 345 : 269);
  278. }
  279. ~UpdateUserDialog()
  280. {
  281. titleLabel = nullptr;
  282. contentLabel = nullptr;
  283. okButton = nullptr;
  284. cancelButton = nullptr;
  285. changeLogLabel = nullptr;
  286. changeLog = nullptr;
  287. overwriteLabel = nullptr;
  288. overwritePath = nullptr;
  289. overwriteButton = nullptr;
  290. juceIcon = nullptr;
  291. }
  292. void paint (Graphics& g) override
  293. {
  294. g.fillAll (Colours::lightgrey);
  295. g.setColour (Colours::black);
  296. if (juceIcon != nullptr)
  297. juceIcon->drawWithin (g, Rectangle<float> (20, 17, 64, 64),
  298. RectanglePlacement::stretchToFit, 1.000f);
  299. }
  300. void resized() override
  301. {
  302. titleLabel->setBounds (88, 10, 397, 24);
  303. contentLabel->setBounds (88, 40, 397, 51);
  304. changeLogLabel->setBounds (22, 92, 341, 24);
  305. changeLog->setBounds (24, 112, 476, 102);
  306. if (hasOverwriteButton)
  307. {
  308. okButton->setBounds (getWidth() - 24 - 174, getHeight() - 37, 174, 28);
  309. overwriteButton->setBounds ((getWidth() - 24 - 174) + -14 - 86, getHeight() - 37, 86, 28);
  310. cancelButton->setBounds (24, getHeight() - 37, 70, 28);
  311. overwriteLabel->setBounds (24, 238, 472, 16);
  312. overwritePath->setBounds (24, 262, 472, 40);
  313. }
  314. else
  315. {
  316. okButton->setBounds (getWidth() - 24 - 47, getHeight() - 37, 47, 28);
  317. cancelButton->setBounds ((getWidth() - 24 - 47) + -14 - 70, getHeight() - 37, 70, 28);
  318. }
  319. }
  320. void buttonClicked (Button* clickedButton) override
  321. {
  322. if (DialogWindow* parentDialog = findParentComponentOfClass<DialogWindow>())
  323. {
  324. if (clickedButton == overwriteButton) parentDialog->exitModalState (1);
  325. else if (clickedButton == okButton) parentDialog->exitModalState (2);
  326. else if (clickedButton == cancelButton) parentDialog->exitModalState (-1);
  327. }
  328. else
  329. jassertfalse;
  330. }
  331. static DialogWindow* launch (const LatestVersionChecker::JuceVersionTriple& version,
  332. const String& releaseNotes,
  333. const char* overwritePath = nullptr)
  334. {
  335. OptionalScopedPointer<Component> userDialog (new UpdateUserDialog (version, releaseNotes, overwritePath), true);
  336. DialogWindow::LaunchOptions lo;
  337. lo.dialogTitle = TRANS ("Download JUCE version 123?").replace ("123", version.toString());
  338. lo.dialogBackgroundColour = Colours::lightgrey;
  339. lo.content = userDialog;
  340. lo.componentToCentreAround = nullptr;
  341. lo.escapeKeyTriggersCloseButton = true;
  342. lo.useNativeTitleBar = true;
  343. lo.resizable = false;
  344. lo.useBottomRightCornerResizer = false;
  345. return lo.launchAsync();
  346. }
  347. private:
  348. bool hasOverwriteButton;
  349. ScopedPointer<Label> titleLabel, contentLabel, changeLogLabel, overwriteLabel, overwritePath;
  350. ScopedPointer<TextButton> okButton, cancelButton;
  351. ScopedPointer<TextEditor> changeLog;
  352. ScopedPointer<TextButton> overwriteButton;
  353. ScopedPointer<Drawable> juceIcon;
  354. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UpdateUserDialog)
  355. };
  356. //==============================================================================
  357. class UpdaterDialogModalCallback : public ModalComponentManager::Callback
  358. {
  359. public:
  360. struct DelayedCallback : private Timer
  361. {
  362. DelayedCallback (LatestVersionChecker& versionChecker,
  363. URL& newVersionToDownload,
  364. const String& extraHeaders,
  365. const File& appParentFolder,
  366. int returnValue)
  367. : parent (versionChecker), download (newVersionToDownload),
  368. headers (extraHeaders), folder (appParentFolder), result (returnValue)
  369. {
  370. startTimer (200);
  371. }
  372. private:
  373. void timerCallback() override
  374. {
  375. stopTimer();
  376. parent.modalStateFinished (result, download, headers, folder);
  377. delete this;
  378. }
  379. LatestVersionChecker& parent;
  380. URL download;
  381. String headers;
  382. File folder;
  383. int result;
  384. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayedCallback)
  385. };
  386. UpdaterDialogModalCallback (LatestVersionChecker& versionChecker,
  387. URL& newVersionToDownload,
  388. const String& extraHeaders,
  389. const File& appParentFolder)
  390. : parent (versionChecker), download (newVersionToDownload),
  391. headers (extraHeaders), folder (appParentFolder)
  392. {}
  393. void modalStateFinished (int returnValue) override
  394. {
  395. // the dialog window is only closed after this function exits
  396. // so we need a deferred callback to the parent. Unfortunately
  397. // our instance is also deleted after this function is used
  398. // so we can't use our own instance for a timer callback
  399. // we must allocate a new one.
  400. new DelayedCallback (parent, download, headers, folder, returnValue);
  401. }
  402. private:
  403. LatestVersionChecker& parent;
  404. URL download;
  405. String headers;
  406. File folder;
  407. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UpdaterDialogModalCallback)
  408. };
  409. //==============================================================================
  410. LatestVersionChecker::LatestVersionChecker() : Thread ("Updater"),
  411. statusCode (-1),
  412. hasAttemptedToReadWebsite (false)
  413. {
  414. startTimer (2000);
  415. }
  416. LatestVersionChecker::~LatestVersionChecker()
  417. {
  418. stopThread (20000);
  419. }
  420. String LatestVersionChecker::getOSString()
  421. {
  422. SystemStats::OperatingSystemType osType = SystemStats::getOperatingSystemType();
  423. if ((osType & SystemStats::MacOSX) != 0) return "OSX";
  424. else if ((osType & SystemStats::Windows) != 0) return "Windows";
  425. else if ((osType & SystemStats::Linux) != 0) return "Linux";
  426. else return SystemStats::getOperatingSystemName();
  427. }
  428. URL LatestVersionChecker::getLatestVersionURL (String& headers, const String& path)
  429. {
  430. static const char* updateSeverHostname = "https://my.roli.com";
  431. static const char* publicAPIKey = "495fb2d-cce9a8-3c52824-2da2679";
  432. static const int apiVersion = 1;
  433. static const char* updatePath = "/software_versions/update_to/Introjucer/";
  434. String updateURL;
  435. bool isAbsolute = (path.startsWith ("http://") || path.startsWith ("https://"));
  436. bool isRedirect = path.isNotEmpty();
  437. if (isAbsolute)
  438. {
  439. updateURL = path;
  440. }
  441. else
  442. {
  443. updateURL << updateSeverHostname
  444. << (isRedirect ? path : String (updatePath));
  445. if (! isRedirect)
  446. {
  447. updateURL << JuceVersionTriple().toString() << '/'
  448. << getOSString() << "?language=" << SystemStats::getUserLanguage();
  449. }
  450. }
  451. headers.clear();
  452. if (! isAbsolute)
  453. {
  454. headers << "X-API-Key: " << publicAPIKey;
  455. if (! isRedirect)
  456. {
  457. headers << "\nContent-Type: application/json\n"
  458. << "Accept: application/json; version=" << apiVersion;
  459. }
  460. }
  461. return URL (updateURL);
  462. }
  463. URL LatestVersionChecker::getLatestVersionURL (String& headers)
  464. {
  465. String emptyString;
  466. return getLatestVersionURL (headers, emptyString);
  467. }
  468. void LatestVersionChecker::checkForNewVersion()
  469. {
  470. hasAttemptedToReadWebsite = true;
  471. {
  472. String extraHeaders;
  473. URL updateURL (getLatestVersionURL (extraHeaders));
  474. StringPairArray responseHeaders;
  475. const int numRedirects = 0;
  476. const ScopedPointer<InputStream> in (updateURL.createInputStream (false, nullptr, nullptr,
  477. extraHeaders, 0, &responseHeaders,
  478. &statusCode, numRedirects));
  479. if (in == nullptr || threadShouldExit())
  480. return; // can't connect: fail silently.
  481. if (statusCode != 303 && statusCode != 400)
  482. return;
  483. // if this doesn't fail then there is a new version available.
  484. // By leaving the scope of this function we will abort the download
  485. // to give the user a chance to cancel an update
  486. if (statusCode == 303)
  487. {
  488. newRelativeDownloadPath = responseHeaders ["Location"];
  489. // A 303 without a redirect target? Let's quickly get out of here
  490. if (newRelativeDownloadPath.isEmpty())
  491. return;
  492. }
  493. jsonReply = JSON::parse (in->readEntireStreamAsString());
  494. }
  495. if (threadShouldExit())
  496. return;
  497. if (jsonReply.isObject())
  498. startTimer (100);
  499. }
  500. void LatestVersionChecker::processResult (var reply, const String& downloadPath)
  501. {
  502. if (statusCode == 303)
  503. {
  504. String versionString = reply.getProperty ("version", var()).toString();
  505. String releaseNotes = reply.getProperty ("notes", var()).toString();
  506. JuceVersionTriple version;
  507. if (versionString.isNotEmpty() && releaseNotes.isNotEmpty())
  508. {
  509. if (JuceVersionTriple::fromString (versionString, version))
  510. {
  511. String extraHeaders;
  512. URL newVersionToDownload = getLatestVersionURL (extraHeaders, downloadPath);
  513. askUserAboutNewVersion (version, releaseNotes, newVersionToDownload, extraHeaders);
  514. }
  515. }
  516. }
  517. else if (statusCode == 400)
  518. {
  519. // In the far-distant future, this may be contacting a defunct
  520. // URL, so hopefully the website will contain a helpful message
  521. // for the user..
  522. var errorObj = reply.getDynamicObject()->getProperty ("error");
  523. if (errorObj.isObject())
  524. {
  525. String message = errorObj.getProperty ("message", var()).toString();
  526. if (message.isNotEmpty())
  527. {
  528. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  529. TRANS("JUCE Updater"),
  530. message);
  531. }
  532. }
  533. }
  534. }
  535. void LatestVersionChecker::askUserAboutNewVersion (const LatestVersionChecker::JuceVersionTriple& version,
  536. const String& releaseNotes,
  537. URL& newVersionToDownload,
  538. const String& extraHeaders)
  539. {
  540. // Currently we do not show the release notes
  541. ignoreUnused (releaseNotes);
  542. JuceVersionTriple currentVersion;
  543. if (version > currentVersion)
  544. {
  545. File appParentFolder (File::getSpecialLocation (File::currentApplicationFile).getParentDirectory());
  546. DialogWindow* modalDialog = nullptr;
  547. if (isZipFolder (appParentFolder))
  548. {
  549. modalDialog = UpdateUserDialog::launch (version, releaseNotes,
  550. appParentFolder.getFullPathName().toRawUTF8());
  551. }
  552. else
  553. {
  554. modalDialog = UpdateUserDialog::launch (version, releaseNotes);
  555. }
  556. if (modalDialog != nullptr)
  557. {
  558. UpdaterDialogModalCallback* callback = new UpdaterDialogModalCallback (*this,
  559. newVersionToDownload,
  560. extraHeaders,
  561. appParentFolder);
  562. // attachCallback will delete callback
  563. if (ModalComponentManager* mm = ModalComponentManager::getInstance())
  564. mm->attachCallback (modalDialog, callback);
  565. }
  566. }
  567. }
  568. void LatestVersionChecker::modalStateFinished (int result,
  569. URL& newVersionToDownload,
  570. const String& extraHeaders,
  571. File appParentFolder)
  572. {
  573. if (result == 1)
  574. DownloadNewVersionThread::performDownload (newVersionToDownload, extraHeaders, appParentFolder);
  575. if (result == 2)
  576. askUserForLocationToDownload (newVersionToDownload, extraHeaders);
  577. }
  578. void LatestVersionChecker::askUserForLocationToDownload (URL& newVersionToDownload, const String& extraHeaders)
  579. {
  580. File targetFolder (findDefaultModulesFolder());
  581. if (isJuceModulesFolder (targetFolder))
  582. targetFolder = targetFolder.getParentDirectory();
  583. FileChooser chooser (TRANS("Please select the location into which you'd like to install the new version"),
  584. targetFolder);
  585. if (chooser.browseForDirectory())
  586. {
  587. targetFolder = chooser.getResult();
  588. if (isJuceModulesFolder (targetFolder))
  589. targetFolder = targetFolder.getParentDirectory();
  590. if (targetFolder.getChildFile ("JUCE").isDirectory())
  591. targetFolder = targetFolder.getChildFile ("JUCE");
  592. if (targetFolder.getChildFile (".git").isDirectory())
  593. {
  594. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  595. TRANS ("Downloading new JUCE version"),
  596. TRANS ("This folder is a GIT repository!\n\n"
  597. "You should use a \"git pull\" to update it to the latest version. "
  598. "Or to use the Introjucer to get an update, you should select an empty "
  599. "folder into which you'd like to download the new code."));
  600. return;
  601. }
  602. if (isJuceFolder (targetFolder))
  603. {
  604. if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
  605. TRANS("Overwrite existing JUCE folder?"),
  606. TRANS("Do you want to overwrite the folder:\n\n"
  607. "xfldrx\n\n"
  608. " ..with the latest version from juce.com?\n\n"
  609. "(Please note that this will overwrite everything in that folder!)")
  610. .replace ("xfldrx", targetFolder.getFullPathName())))
  611. {
  612. return;
  613. }
  614. }
  615. else
  616. {
  617. targetFolder = targetFolder.getChildFile ("JUCE").getNonexistentSibling();
  618. }
  619. DownloadNewVersionThread::performDownload (newVersionToDownload, extraHeaders, targetFolder);
  620. }
  621. }
  622. bool LatestVersionChecker::isZipFolder (const File& f)
  623. {
  624. return f.getChildFile ("modules").isDirectory()
  625. && f.getChildFile ("extras").isDirectory()
  626. && f.getChildFile ("examples").isDirectory()
  627. && ! f.getChildFile (".git").isDirectory();
  628. }
  629. void LatestVersionChecker::timerCallback()
  630. {
  631. stopTimer();
  632. if (hasAttemptedToReadWebsite)
  633. processResult (jsonReply, newRelativeDownloadPath);
  634. else
  635. startThread (3);
  636. }
  637. void LatestVersionChecker::run()
  638. {
  639. checkForNewVersion();
  640. }