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.

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