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.

839 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 "../Application/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. std::unique_ptr<InputStream> in;
  145. for (int redirect = 0; redirect < maxRedirects; ++redirect)
  146. {
  147. StringPairArray responseHeaders;
  148. in.reset (url.createInputStream (false, nullptr, nullptr, headers,
  149. 10000, &responseHeaders, &statusCode, 0));
  150. if (in == nullptr || statusCode != 302)
  151. break;
  152. String redirectPath = responseHeaders ["Location"];
  153. if (redirectPath.isEmpty())
  154. break;
  155. url = owner.getLatestVersionURL (headers, redirectPath);
  156. }
  157. if (in != nullptr && statusCode == 200)
  158. {
  159. int64 total = 0;
  160. MemoryOutputStream mo (dest, true);
  161. for (;;)
  162. {
  163. if (threadShouldExit())
  164. return Result::fail ("cancel");
  165. int64 written = mo.writeFromInputStream (*in, 8192);
  166. if (written == 0)
  167. break;
  168. total += written;
  169. setStatusMessage (String (TRANS("Downloading... (123)"))
  170. .replace ("123", File::descriptionOfSizeInBytes (total)));
  171. }
  172. return Result::ok();
  173. }
  174. return Result::fail ("Failed to download from: " + url.toString (false));
  175. }
  176. LatestVersionChecker& owner;
  177. Result result;
  178. URL url;
  179. String headers;
  180. File targetFolder;
  181. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadNewVersionThread)
  182. };
  183. //==============================================================================
  184. class UpdateUserDialog : public Component
  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. titleLabel.reset (new Label ("Title Label",
  194. TRANS("Download \"123\" version 456?")
  195. .replace ("123", productName)
  196. .replace ("456", version.toString())));
  197. addAndMakeVisible (titleLabel.get());
  198. titleLabel->setFont (Font (15.00f, Font::bold));
  199. titleLabel->setJustificationType (Justification::centredLeft);
  200. titleLabel->setEditable (false, false, false);
  201. contentLabel.reset(new Label ("Content Label",
  202. TRANS("A new version of \"123\" is available - would you like to download it?")
  203. .replace ("123", productName)));
  204. addAndMakeVisible (contentLabel.get());
  205. contentLabel->setFont (Font (15.00f, Font::plain));
  206. contentLabel->setJustificationType (Justification::topLeft);
  207. contentLabel->setEditable (false, false, false);
  208. okButton.reset (new TextButton ("OK Button"));
  209. addAndMakeVisible (okButton.get());
  210. okButton->setButtonText (TRANS(hasOverwriteButton ? "Choose Another Folder..." : "OK"));
  211. okButton->onClick = [this] { exitParentDialog (2); };
  212. cancelButton.reset (new TextButton ("Cancel Button"));
  213. addAndMakeVisible (cancelButton.get());
  214. cancelButton->setButtonText (TRANS("Cancel"));
  215. cancelButton->onClick = [this] { exitParentDialog (-1); };
  216. changeLogLabel.reset (new Label ("Change Log Label", TRANS("Release Notes:")));
  217. addAndMakeVisible (changeLogLabel.get());
  218. changeLogLabel->setFont (Font (15.00f, Font::plain));
  219. changeLogLabel->setJustificationType (Justification::topLeft);
  220. changeLogLabel->setEditable (false, false, false);
  221. changeLog.reset (new TextEditor ("Change Log"));
  222. addAndMakeVisible (changeLog.get());
  223. changeLog->setMultiLine (true);
  224. changeLog->setReturnKeyStartsNewLine (true);
  225. changeLog->setReadOnly (true);
  226. changeLog->setScrollbarsShown (true);
  227. changeLog->setCaretVisible (false);
  228. changeLog->setPopupMenuEnabled (false);
  229. changeLog->setText (releaseNotes);
  230. if (hasOverwriteButton)
  231. {
  232. overwriteLabel.reset (new Label ("Overwrite Label",
  233. TRANS("Updating will overwrite everything in the following folder:")));
  234. addAndMakeVisible (overwriteLabel.get());
  235. overwriteLabel->setFont (Font (15.00f, Font::plain));
  236. overwriteLabel->setJustificationType (Justification::topLeft);
  237. overwriteLabel->setEditable (false, false, false);
  238. overwritePath.reset (new Label ("Overwrite Path", overwriteFolderPath));
  239. addAndMakeVisible (overwritePath.get());
  240. overwritePath->setFont (Font (15.00f, Font::bold));
  241. overwritePath->setJustificationType (Justification::topLeft);
  242. overwritePath->setEditable (false, false, false);
  243. overwriteButton.reset (new TextButton ("Overwrite Button"));
  244. addAndMakeVisible (overwriteButton.get());
  245. overwriteButton->setButtonText (TRANS("Overwrite"));
  246. overwriteButton->onClick = [this] { exitParentDialog (1); };
  247. }
  248. juceIcon.reset (Drawable::createFromImageData (BinaryData::juce_icon_png,
  249. BinaryData::juce_icon_pngSize));
  250. setSize (518, overwritePath != nullptr ? 345 : 269);
  251. lookAndFeelChanged();
  252. }
  253. ~UpdateUserDialog()
  254. {
  255. titleLabel.reset();
  256. contentLabel.reset();
  257. okButton.reset();
  258. cancelButton.reset();
  259. changeLogLabel.reset();
  260. changeLog.reset();
  261. overwriteLabel.reset();
  262. overwritePath.reset();
  263. overwriteButton.reset();
  264. juceIcon.reset();
  265. }
  266. void paint (Graphics& g) override
  267. {
  268. g.fillAll (findColour (backgroundColourId));
  269. g.setColour (findColour (defaultTextColourId));
  270. if (juceIcon != nullptr)
  271. juceIcon->drawWithin (g, Rectangle<float> (20, 17, 64, 64),
  272. RectanglePlacement::stretchToFit, 1.000f);
  273. }
  274. void resized() override
  275. {
  276. titleLabel->setBounds (88, 10, 397, 24);
  277. contentLabel->setBounds (88, 40, 397, 51);
  278. changeLogLabel->setBounds (22, 92, 341, 24);
  279. changeLog->setBounds (24, 112, 476, 102);
  280. if (hasOverwriteButton)
  281. {
  282. okButton->setBounds (getWidth() - 24 - 174, getHeight() - 37, 174, 28);
  283. overwriteButton->setBounds ((getWidth() - 24 - 174) + -14 - 86, getHeight() - 37, 86, 28);
  284. cancelButton->setBounds (24, getHeight() - 37, 70, 28);
  285. overwriteLabel->setBounds (24, 238, 472, 16);
  286. overwritePath->setBounds (24, 262, 472, 40);
  287. }
  288. else
  289. {
  290. okButton->setBounds (getWidth() - 24 - 47, getHeight() - 37, 47, 28);
  291. cancelButton->setBounds ((getWidth() - 24 - 47) + -14 - 70, getHeight() - 37, 70, 28);
  292. }
  293. }
  294. void exitParentDialog (int returnVal)
  295. {
  296. if (auto* parentDialog = findParentComponentOfClass<DialogWindow>())
  297. parentDialog->exitModalState (returnVal);
  298. else
  299. jassertfalse;
  300. }
  301. static DialogWindow* launch (const LatestVersionChecker::JuceVersionTriple& version,
  302. const String& productName,
  303. const String& releaseNotes,
  304. const char* overwritePath = nullptr)
  305. {
  306. OptionalScopedPointer<Component> userDialog (new UpdateUserDialog (version, productName,
  307. releaseNotes, overwritePath), true);
  308. DialogWindow::LaunchOptions lo;
  309. lo.dialogTitle = TRANS("Download \"123\" version 456?").replace ("456", version.toString())
  310. .replace ("123", productName);
  311. lo.dialogBackgroundColour = userDialog->findColour (backgroundColourId);
  312. lo.content = userDialog;
  313. lo.componentToCentreAround = nullptr;
  314. lo.escapeKeyTriggersCloseButton = true;
  315. lo.useNativeTitleBar = true;
  316. lo.resizable = false;
  317. lo.useBottomRightCornerResizer = false;
  318. return lo.launchAsync();
  319. }
  320. private:
  321. bool hasOverwriteButton;
  322. std::unique_ptr<Label> titleLabel, contentLabel, changeLogLabel, overwriteLabel, overwritePath;
  323. std::unique_ptr<TextButton> okButton, cancelButton;
  324. std::unique_ptr<TextEditor> changeLog;
  325. std::unique_ptr<TextButton> overwriteButton;
  326. std::unique_ptr<Drawable> juceIcon;
  327. void lookAndFeelChanged() override
  328. {
  329. cancelButton->setColour (TextButton::buttonColourId,
  330. findColour (secondaryButtonBackgroundColourId));
  331. changeLog->applyFontToAllText (changeLog->getFont());
  332. }
  333. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UpdateUserDialog)
  334. };
  335. //==============================================================================
  336. class UpdaterDialogModalCallback : public ModalComponentManager::Callback
  337. {
  338. public:
  339. struct DelayedCallback : private Timer
  340. {
  341. DelayedCallback (LatestVersionChecker& versionChecker,
  342. URL& newVersionToDownload,
  343. const String& extraHeaders,
  344. const File& appParentFolder,
  345. int returnValue)
  346. : parent (versionChecker), download (newVersionToDownload),
  347. headers (extraHeaders), folder (appParentFolder), result (returnValue)
  348. {
  349. startTimer (200);
  350. }
  351. private:
  352. void timerCallback() override
  353. {
  354. stopTimer();
  355. parent.modalStateFinished (result, download, headers, folder);
  356. delete this;
  357. }
  358. LatestVersionChecker& parent;
  359. URL download;
  360. String headers;
  361. File folder;
  362. int result;
  363. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayedCallback)
  364. };
  365. UpdaterDialogModalCallback (LatestVersionChecker& versionChecker,
  366. URL& newVersionToDownload,
  367. const String& extraHeaders,
  368. const File& appParentFolder)
  369. : parent (versionChecker), download (newVersionToDownload),
  370. headers (extraHeaders), folder (appParentFolder)
  371. {}
  372. void modalStateFinished (int returnValue) override
  373. {
  374. // the dialog window is only closed after this function exits
  375. // so we need a deferred callback to the parent. Unfortunately
  376. // our instance is also deleted after this function is used
  377. // so we can't use our own instance for a timer callback
  378. // we must allocate a new one.
  379. new DelayedCallback (parent, download, headers, folder, returnValue);
  380. }
  381. private:
  382. LatestVersionChecker& parent;
  383. URL download;
  384. String headers;
  385. File folder;
  386. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UpdaterDialogModalCallback)
  387. };
  388. //==============================================================================
  389. LatestVersionChecker::LatestVersionChecker() : Thread ("Updater"),
  390. statusCode (-1),
  391. hasAttemptedToReadWebsite (false)
  392. {
  393. startTimer (2000);
  394. }
  395. LatestVersionChecker::~LatestVersionChecker()
  396. {
  397. stopThread (20000);
  398. }
  399. String LatestVersionChecker::getOSString()
  400. {
  401. SystemStats::OperatingSystemType osType = SystemStats::getOperatingSystemType();
  402. if ((osType & SystemStats::MacOSX) != 0) return "OSX";
  403. else if ((osType & SystemStats::Windows) != 0) return "Windows";
  404. else if ((osType & SystemStats::Linux) != 0) return "Linux";
  405. else return SystemStats::getOperatingSystemName();
  406. }
  407. const LatestVersionChecker::JuceServerLocationsAndKeys& LatestVersionChecker::getJuceServerURLsAndKeys() const
  408. {
  409. static LatestVersionChecker::JuceServerLocationsAndKeys urlsAndKeys =
  410. {
  411. "https://my.roli.com",
  412. "265441b-343403c-20f6932-76361d",
  413. 1,
  414. "/software_versions/update_to/Projucer/"
  415. };
  416. return urlsAndKeys;
  417. }
  418. int LatestVersionChecker::getProductVersionNumber() const { return ProjectInfo::versionNumber; }
  419. const char* LatestVersionChecker::getProductName() const { return ProjectInfo::projectName; }
  420. bool LatestVersionChecker::allowCustomLocation() const { return true; }
  421. Result LatestVersionChecker::performUpdate (const MemoryBlock& data, File& targetFolder)
  422. {
  423. File unzipTarget;
  424. bool isUsingTempFolder = false;
  425. {
  426. MemoryInputStream input (data, false);
  427. ZipFile zip (input);
  428. if (zip.getNumEntries() == 0)
  429. return Result::fail ("The downloaded file wasn't a valid JUCE file!");
  430. unzipTarget = targetFolder;
  431. if (unzipTarget.exists())
  432. {
  433. isUsingTempFolder = true;
  434. unzipTarget = targetFolder.getNonexistentSibling();
  435. if (! unzipTarget.createDirectory())
  436. return Result::fail ("Couldn't create a folder to unzip the new version!");
  437. }
  438. Result r (zip.uncompressTo (unzipTarget));
  439. if (r.failed())
  440. {
  441. if (isUsingTempFolder)
  442. unzipTarget.deleteRecursively();
  443. return r;
  444. }
  445. }
  446. if (isUsingTempFolder)
  447. {
  448. File oldFolder (targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old")
  449. .getNonexistentSibling());
  450. if (! targetFolder.moveFileTo (oldFolder))
  451. {
  452. unzipTarget.deleteRecursively();
  453. return Result::fail ("Could not remove the existing folder!");
  454. }
  455. if (! unzipTarget.moveFileTo (targetFolder))
  456. {
  457. unzipTarget.deleteRecursively();
  458. return Result::fail ("Could not overwrite the existing folder!");
  459. }
  460. }
  461. return Result::ok();
  462. }
  463. URL LatestVersionChecker::getLatestVersionURL (String& headers, const String& path) const
  464. {
  465. const LatestVersionChecker::JuceServerLocationsAndKeys& urlsAndKeys = getJuceServerURLsAndKeys();
  466. String updateURL;
  467. bool isAbsolute = (path.startsWith ("http://") || path.startsWith ("https://"));
  468. bool isRedirect = path.isNotEmpty();
  469. if (isAbsolute)
  470. {
  471. updateURL = path;
  472. }
  473. else
  474. {
  475. updateURL << urlsAndKeys.updateSeverHostname
  476. << (isRedirect ? path : String (urlsAndKeys.updatePath));
  477. if (! isRedirect)
  478. {
  479. updateURL << JuceVersionTriple (getProductVersionNumber()).toString() << '/'
  480. << getOSString() << "?language=" << SystemStats::getUserLanguage();
  481. }
  482. }
  483. headers.clear();
  484. if (! isAbsolute)
  485. {
  486. headers << "X-API-Key: " << urlsAndKeys.publicAPIKey;
  487. if (! isRedirect)
  488. {
  489. headers << "\nContent-Type: application/json\n"
  490. << "Accept: application/json; version=" << urlsAndKeys.apiVersion;
  491. }
  492. }
  493. return URL (updateURL);
  494. }
  495. URL LatestVersionChecker::getLatestVersionURL (String& headers) const
  496. {
  497. String emptyString;
  498. return getLatestVersionURL (headers, emptyString);
  499. }
  500. void LatestVersionChecker::checkForNewVersion()
  501. {
  502. hasAttemptedToReadWebsite = true;
  503. {
  504. String extraHeaders;
  505. URL updateURL (getLatestVersionURL (extraHeaders));
  506. StringPairArray responseHeaders;
  507. const int numRedirects = 0;
  508. const std::unique_ptr<InputStream> in (updateURL.createInputStream (false, nullptr, nullptr,
  509. extraHeaders, 0, &responseHeaders,
  510. &statusCode, numRedirects));
  511. if (threadShouldExit())
  512. return; // can't connect: fail silently.
  513. if (in != nullptr && (statusCode == 303 || statusCode == 400))
  514. {
  515. // if this doesn't fail then there is a new version available.
  516. // By leaving the scope of this function we will abort the download
  517. // to give the user a chance to cancel an update
  518. if (statusCode == 303)
  519. newRelativeDownloadPath = responseHeaders ["Location"];
  520. jsonReply = JSON::parse (in->readEntireStreamAsString());
  521. }
  522. }
  523. if (! threadShouldExit())
  524. startTimer (100);
  525. }
  526. bool LatestVersionChecker::processResult (var reply, const String& downloadPath)
  527. {
  528. if (statusCode == 303)
  529. {
  530. String versionString = reply.getProperty ("version", var()).toString();
  531. String releaseNotes = reply.getProperty ("notes", var()).toString();
  532. JuceVersionTriple version;
  533. if (versionString.isNotEmpty() && releaseNotes.isNotEmpty())
  534. {
  535. if (JuceVersionTriple::fromString (versionString, version))
  536. {
  537. String extraHeaders;
  538. URL newVersionToDownload = getLatestVersionURL (extraHeaders, downloadPath);
  539. return askUserAboutNewVersion (version, releaseNotes, newVersionToDownload, extraHeaders);
  540. }
  541. }
  542. }
  543. else if (statusCode == 400)
  544. {
  545. // In the far-distant future, this may be contacting a defunct
  546. // URL, so hopefully the website will contain a helpful message
  547. // for the user..
  548. var errorObj = reply.getDynamicObject()->getProperty ("error");
  549. if (errorObj.isObject())
  550. {
  551. String message = errorObj.getProperty ("message", var()).toString();
  552. if (message.isNotEmpty())
  553. {
  554. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  555. TRANS("JUCE Updater"),
  556. message);
  557. return false;
  558. }
  559. }
  560. }
  561. // try again
  562. return true;
  563. }
  564. bool LatestVersionChecker::askUserAboutNewVersion (const LatestVersionChecker::JuceVersionTriple& version,
  565. const String& releaseNotes,
  566. URL& newVersionToDownload,
  567. const String& extraHeaders)
  568. {
  569. JuceVersionTriple currentVersion (getProductVersionNumber());
  570. if (version > currentVersion)
  571. {
  572. File appParentFolder (File::getSpecialLocation (File::currentApplicationFile).getParentDirectory());
  573. DialogWindow* modalDialog = nullptr;
  574. if (isZipFolder (appParentFolder) && allowCustomLocation())
  575. {
  576. modalDialog = UpdateUserDialog::launch (version, getProductName(), releaseNotes,
  577. appParentFolder.getFullPathName().toRawUTF8());
  578. }
  579. else
  580. {
  581. modalDialog = UpdateUserDialog::launch (version, getProductName(), releaseNotes);
  582. }
  583. if (modalDialog != nullptr)
  584. {
  585. UpdaterDialogModalCallback* callback = new UpdaterDialogModalCallback (*this,
  586. newVersionToDownload,
  587. extraHeaders,
  588. appParentFolder);
  589. // attachCallback will delete callback
  590. if (ModalComponentManager* mm = ModalComponentManager::getInstance())
  591. mm->attachCallback (modalDialog, callback);
  592. }
  593. return false;
  594. }
  595. return true;
  596. }
  597. void LatestVersionChecker::modalStateFinished (int result,
  598. URL& newVersionToDownload,
  599. const String& extraHeaders,
  600. File appParentFolder)
  601. {
  602. if (result == 1 || result == 2)
  603. {
  604. if (result == 1 || ! allowCustomLocation())
  605. DownloadNewVersionThread::performDownload (*this, newVersionToDownload, extraHeaders, appParentFolder);
  606. else
  607. askUserForLocationToDownload (newVersionToDownload, extraHeaders);
  608. }
  609. }
  610. void LatestVersionChecker::askUserForLocationToDownload (URL& newVersionToDownload, const String& extraHeaders)
  611. {
  612. File targetFolder (EnabledModuleList::findGlobalModulesFolder());
  613. if (isJUCEModulesFolder (targetFolder))
  614. targetFolder = targetFolder.getParentDirectory();
  615. FileChooser chooser (TRANS("Please select the location into which you'd like to install the new version"),
  616. targetFolder);
  617. if (chooser.browseForDirectory())
  618. {
  619. targetFolder = chooser.getResult();
  620. if (isJUCEModulesFolder (targetFolder))
  621. targetFolder = targetFolder.getParentDirectory();
  622. if (targetFolder.getChildFile ("JUCE").isDirectory())
  623. targetFolder = targetFolder.getChildFile ("JUCE");
  624. if (targetFolder.getChildFile (".git").isDirectory())
  625. {
  626. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  627. TRANS("Downloading new JUCE version"),
  628. TRANS("This folder is a GIT repository!\n\n"
  629. "You should use a \"git pull\" to update it to the latest version. "
  630. "Or to use the Projucer to get an update, you should select an empty "
  631. "folder into which you'd like to download the new code."));
  632. return;
  633. }
  634. if (isJUCEFolder (targetFolder))
  635. {
  636. if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
  637. TRANS("Overwrite existing JUCE folder?"),
  638. TRANS("Do you want to overwrite the folder:\n\n"
  639. "xfldrx\n\n"
  640. " ..with the latest version from juce.com?\n\n"
  641. "(Please note that this will overwrite everything in that folder!)")
  642. .replace ("xfldrx", targetFolder.getFullPathName())))
  643. {
  644. return;
  645. }
  646. }
  647. else
  648. {
  649. targetFolder = targetFolder.getChildFile ("JUCE").getNonexistentSibling();
  650. }
  651. DownloadNewVersionThread::performDownload (*this, newVersionToDownload, extraHeaders, targetFolder);
  652. }
  653. }
  654. bool LatestVersionChecker::isZipFolder (const File& f)
  655. {
  656. return f.getChildFile ("modules").isDirectory()
  657. && f.getChildFile ("extras").isDirectory()
  658. && f.getChildFile ("examples").isDirectory()
  659. && ! f.getChildFile (".git").isDirectory();
  660. }
  661. void LatestVersionChecker::timerCallback()
  662. {
  663. stopTimer();
  664. if (hasAttemptedToReadWebsite)
  665. {
  666. bool restartTimer = true;
  667. if (jsonReply.isObject())
  668. restartTimer = processResult (jsonReply, newRelativeDownloadPath);
  669. hasAttemptedToReadWebsite = false;
  670. if (restartTimer)
  671. startTimer (7200000);
  672. }
  673. else
  674. {
  675. startThread (3);
  676. }
  677. }
  678. void LatestVersionChecker::run()
  679. {
  680. checkForNewVersion();
  681. }