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.

405 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../jucer_Headers.h"
  19. #include "jucer_JuceUpdater.h"
  20. //==============================================================================
  21. JuceUpdater::JuceUpdater()
  22. : filenameComp ("Juce Folder", StoredSettings::getInstance()->getLastKnownJuceFolder(),
  23. true, true, false, "*", String::empty, "Select your Juce folder"),
  24. checkNowButton ("Check Online for Available Updates...",
  25. "Contacts the website to see if this version is up-to-date")
  26. {
  27. addAndMakeVisible (&label);
  28. addAndMakeVisible (&filenameComp);
  29. addAndMakeVisible (&checkNowButton);
  30. addAndMakeVisible (&currentVersionLabel);
  31. checkNowButton.addListener (this);
  32. filenameComp.addListener (this);
  33. currentVersionLabel.setFont (Font (14.0f, Font::italic));
  34. label.setFont (Font (12.0f));
  35. label.setText ("Destination folder:", false);
  36. addAndMakeVisible (&availableVersionsList);
  37. availableVersionsList.setModel (this);
  38. setSize (600, 300);
  39. }
  40. JuceUpdater::~JuceUpdater()
  41. {
  42. checkNowButton.removeListener (this);
  43. filenameComp.removeListener (this);
  44. }
  45. void JuceUpdater::show (Component* mainWindow)
  46. {
  47. JuceUpdater updater;
  48. DialogWindow::showModalDialog ("Juce Update...", &updater, mainWindow,
  49. Colours::lightgrey,
  50. true, false, false);
  51. }
  52. void JuceUpdater::resized()
  53. {
  54. filenameComp.setBounds (20, 40, getWidth() - 40, 22);
  55. label.setBounds (filenameComp.getX(), filenameComp.getY() - 18, filenameComp.getWidth(), 18);
  56. currentVersionLabel.setBounds (filenameComp.getX(), filenameComp.getBottom(), filenameComp.getWidth(), 25);
  57. checkNowButton.changeWidthToFitText (20);
  58. checkNowButton.setCentrePosition (getWidth() / 2, filenameComp.getBottom() + 40);
  59. availableVersionsList.setBounds (filenameComp.getX(), checkNowButton.getBottom() + 20, filenameComp.getWidth(), getHeight() - (checkNowButton.getBottom() + 20));
  60. }
  61. void JuceUpdater::paint (Graphics& g)
  62. {
  63. g.fillAll (Colours::white);
  64. }
  65. String findVersionNum (const String& file, const String& token)
  66. {
  67. return file.fromFirstOccurrenceOf (token, false, false)
  68. .upToFirstOccurrenceOf ("\n", false, false)
  69. .trim();
  70. }
  71. String JuceUpdater::getCurrentVersion()
  72. {
  73. const String header (filenameComp.getCurrentFile()
  74. .getChildFile ("src/core/juce_StandardHeader.h").loadFileAsString());
  75. const String v1 (findVersionNum (header, "JUCE_MAJOR_VERSION"));
  76. const String v2 (findVersionNum (header, "JUCE_MINOR_VERSION"));
  77. const String v3 (findVersionNum (header, "JUCE_BUILDNUMBER"));
  78. if ((v1 + v2 + v3).isEmpty())
  79. return String::empty;
  80. return v1 + "." + v2 + "." + v3;
  81. }
  82. XmlElement* JuceUpdater::downloadVersionList()
  83. {
  84. return URL ("http://www.rawmaterialsoftware.com/juce/downloads/juce_versions.php").readEntireXmlStream();
  85. }
  86. void JuceUpdater::updateVersions (const XmlElement& xml)
  87. {
  88. availableVersions.clear();
  89. forEachXmlChildElementWithTagName (xml, v, "VERSION")
  90. {
  91. VersionInfo* vi = new VersionInfo();
  92. vi->url = URL (v->getStringAttribute ("url"));
  93. vi->desc = v->getStringAttribute ("desc");
  94. vi->version = v->getStringAttribute ("version");
  95. vi->date = v->getStringAttribute ("date");
  96. availableVersions.add (vi);
  97. }
  98. availableVersionsList.updateContent();
  99. }
  100. void JuceUpdater::buttonClicked (Button*)
  101. {
  102. ScopedPointer<XmlElement> xml (downloadVersionList());
  103. if (xml == nullptr || xml->hasTagName ("html"))
  104. {
  105. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Connection Problems...",
  106. "Couldn't connect to the Raw Material Software website!");
  107. return;
  108. }
  109. if (! xml->hasTagName ("JUCEVERSIONS"))
  110. {
  111. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Update Problems...",
  112. "This version of the Introjucer may be too old to receive automatic updates!\n\n"
  113. "Please visit www.rawmaterialsoftware.com and get the latest version manually!");
  114. return;
  115. }
  116. const String currentVersion (getCurrentVersion());
  117. OwnedArray<VersionInfo> versions;
  118. updateVersions (*xml);
  119. }
  120. //==============================================================================
  121. class NewVersionDownloader : public ThreadWithProgressWindow
  122. {
  123. public:
  124. NewVersionDownloader (const String& title, const URL& url_, const File& target_)
  125. : ThreadWithProgressWindow (title, true, true),
  126. url (url_), target (target_)
  127. {
  128. }
  129. void run()
  130. {
  131. setStatusMessage ("Contacting website...");
  132. ScopedPointer<InputStream> input (url.createInputStream (false));
  133. if (input == nullptr)
  134. {
  135. error = "Couldn't connect to the website...";
  136. return;
  137. }
  138. if (! target.deleteFile())
  139. {
  140. error = "Couldn't delete the destination file...";
  141. return;
  142. }
  143. ScopedPointer<OutputStream> output (target.createOutputStream (32768));
  144. if (output == nullptr)
  145. {
  146. error = "Couldn't write to the destination file...";
  147. return;
  148. }
  149. setStatusMessage ("Downloading...");
  150. int totalBytes = (int) input->getTotalLength();
  151. int bytesSoFar = 0;
  152. while (! (input->isExhausted() || threadShouldExit()))
  153. {
  154. HeapBlock<char> buffer (8192);
  155. const int num = input->read (buffer, 8192);
  156. if (num == 0)
  157. break;
  158. output->write (buffer, num);
  159. bytesSoFar += num;
  160. setProgress (totalBytes > 0 ? bytesSoFar / (double) totalBytes : -1.0);
  161. }
  162. }
  163. String error;
  164. private:
  165. URL url;
  166. File target;
  167. };
  168. //==============================================================================
  169. class Unzipper : public ThreadWithProgressWindow
  170. {
  171. public:
  172. Unzipper (ZipFile& zipFile_, const File& targetDir_)
  173. : ThreadWithProgressWindow ("Unzipping...", true, true),
  174. worked (true), zipFile (zipFile_), targetDir (targetDir_)
  175. {
  176. }
  177. void run()
  178. {
  179. for (int i = 0; i < zipFile.getNumEntries(); ++i)
  180. {
  181. if (threadShouldExit())
  182. break;
  183. const ZipFile::ZipEntry* e = zipFile.getEntry (i);
  184. setStatusMessage ("Unzipping " + e->filename + "...");
  185. setProgress (i / (double) zipFile.getNumEntries());
  186. worked = zipFile.uncompressEntry (i, targetDir, true) && worked;
  187. }
  188. }
  189. bool worked;
  190. private:
  191. ZipFile& zipFile;
  192. File targetDir;
  193. };
  194. //==============================================================================
  195. void JuceUpdater::applyVersion (VersionInfo* version)
  196. {
  197. File destDir (filenameComp.getCurrentFile());
  198. const bool destDirExisted = destDir.isDirectory();
  199. if (destDirExisted && destDir.getNumberOfChildFiles (File::findFilesAndDirectories, "*") > 0)
  200. {
  201. int r = AlertWindow::showYesNoCancelBox (AlertWindow::WarningIcon, "Folder already exists",
  202. "The folder " + destDir.getFullPathName() + "\nalready contains some files...\n\n"
  203. "Do you want to delete everything in the folder and replace it entirely, or just merge the new files into the existing folder?",
  204. "Delete and replace entire folder",
  205. "Add and overwrite existing files",
  206. "Cancel");
  207. if (r == 0)
  208. return;
  209. if (r == 1)
  210. {
  211. if (! destDir.deleteRecursively())
  212. {
  213. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Problems...",
  214. "Couldn't delete the existing folder!");
  215. return;
  216. }
  217. }
  218. }
  219. if (! (destDir.isDirectory() || destDir.createDirectory()))
  220. {
  221. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Problems...",
  222. "Couldn't create that target folder..");
  223. return;
  224. }
  225. File zipFile (destDir.getNonexistentChildFile ("juce_download", ".tar.gz", false));
  226. bool worked = false;
  227. {
  228. NewVersionDownloader downloader ("Downloading Version " + version->version + "...",
  229. version->url, zipFile);
  230. worked = downloader.runThread();
  231. }
  232. if (worked)
  233. {
  234. ZipFile zip (zipFile);
  235. Unzipper unzipper (zip, destDir);
  236. worked = unzipper.runThread() && unzipper.worked;
  237. }
  238. zipFile.deleteFile();
  239. if ((! destDirExisted) && (destDir.getNumberOfChildFiles (File::findFilesAndDirectories, "*") == 0 || ! worked))
  240. destDir.deleteRecursively();
  241. filenameComponentChanged (&filenameComp);
  242. }
  243. void JuceUpdater::filenameComponentChanged (FilenameComponent*)
  244. {
  245. const String version (getCurrentVersion());
  246. if (version.isEmpty())
  247. currentVersionLabel.setText ("(Not a Juce folder)", false);
  248. else
  249. currentVersionLabel.setText ("(Current version in this folder: " + version + ")", false);
  250. }
  251. //==============================================================================
  252. int JuceUpdater::getNumRows()
  253. {
  254. return availableVersions.size();
  255. }
  256. void JuceUpdater::paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
  257. {
  258. if (rowIsSelected)
  259. g.fillAll (findColour (TextEditor::highlightColourId));
  260. }
  261. Component* JuceUpdater::refreshComponentForRow (int rowNumber, bool isRowSelected, Component* existingComponentToUpdate)
  262. {
  263. class UpdateListComponent : public Component,
  264. public ButtonListener
  265. {
  266. public:
  267. UpdateListComponent (JuceUpdater& updater_)
  268. : updater (updater_),
  269. version (nullptr),
  270. applyButton ("Install this version...")
  271. {
  272. addAndMakeVisible (&applyButton);
  273. applyButton.addListener (this);
  274. setInterceptsMouseClicks (false, true);
  275. }
  276. ~UpdateListComponent()
  277. {
  278. applyButton.removeListener (this);
  279. }
  280. void setVersion (VersionInfo* v)
  281. {
  282. if (version != v)
  283. {
  284. version = v;
  285. repaint();
  286. resized();
  287. }
  288. }
  289. void paint (Graphics& g)
  290. {
  291. if (version != nullptr)
  292. {
  293. g.setColour (Colours::green.withAlpha (0.12f));
  294. g.fillRect (0, 1, getWidth(), getHeight() - 2);
  295. g.setColour (Colours::black);
  296. g.setFont (getHeight() * 0.7f);
  297. String s;
  298. s << "Version " << version->version << " - " << version->desc << " - " << version->date;
  299. g.drawText (s, 4, 0, applyButton.getX() - 4, getHeight(), Justification::centredLeft, true);
  300. }
  301. }
  302. void resized()
  303. {
  304. applyButton.changeWidthToFitText (getHeight() - 4);
  305. applyButton.setTopRightPosition (getWidth(), 2);
  306. applyButton.setVisible (version != nullptr);
  307. }
  308. void buttonClicked (Button*)
  309. {
  310. updater.applyVersion (version);
  311. }
  312. private:
  313. JuceUpdater& updater;
  314. VersionInfo* version;
  315. TextButton applyButton;
  316. };
  317. UpdateListComponent* c = dynamic_cast <UpdateListComponent*> (existingComponentToUpdate);
  318. if (c == nullptr)
  319. c = new UpdateListComponent (*this);
  320. c->setVersion (availableVersions [rowNumber]);
  321. return c;
  322. }