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.

829 lines
29KB

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