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.

825 lines
29KB

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