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.

423 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software 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. #ifndef JUCER_AUTOUPDATER_H_INCLUDED
  18. #define JUCER_AUTOUPDATER_H_INCLUDED
  19. //==============================================================================
  20. class LatestVersionChecker : private Thread,
  21. private Timer
  22. {
  23. public:
  24. LatestVersionChecker() : Thread ("Updater"),
  25. hasAttemptedToReadWebsite (false)
  26. {
  27. startTimer (2000);
  28. }
  29. ~LatestVersionChecker()
  30. {
  31. stopThread (20000);
  32. }
  33. static URL getLatestVersionURL()
  34. {
  35. return URL ("http://www.juce.com/juce/updates/updatelist.php");
  36. }
  37. void checkForNewVersion()
  38. {
  39. hasAttemptedToReadWebsite = true;
  40. {
  41. const ScopedPointer<InputStream> in (getLatestVersionURL().createInputStream (false));
  42. if (in == nullptr || threadShouldExit())
  43. return; // can't connect: fail silently.
  44. jsonReply = JSON::parse (in->readEntireStreamAsString());
  45. }
  46. if (threadShouldExit())
  47. return;
  48. if (jsonReply.isArray() || jsonReply.isObject())
  49. startTimer (100);
  50. }
  51. void processResult (var reply)
  52. {
  53. if (reply.isArray())
  54. {
  55. askUserAboutNewVersion (VersionInfo (reply[0]));
  56. }
  57. else if (reply.isObject())
  58. {
  59. // In the far-distant future, this may be contacting a defunct
  60. // URL, so hopefully the website will contain a helpful message
  61. // for the user..
  62. String message = reply.getProperty ("message", var()).toString();
  63. if (message.isNotEmpty())
  64. {
  65. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  66. TRANS("JUCE Updater"),
  67. message);
  68. }
  69. }
  70. }
  71. struct VersionInfo
  72. {
  73. VersionInfo (var v)
  74. {
  75. version = v.getProperty ("version", var()).toString().trim();
  76. url = v.getProperty (
  77. #if JUCE_MAC
  78. "url_osx",
  79. #elif JUCE_WINDOWS
  80. "url_win",
  81. #elif JUCE_LINUX
  82. "url_linux",
  83. #endif
  84. var()).toString();
  85. }
  86. bool isDifferentVersionToCurrent() const
  87. {
  88. return version != JUCE_STRINGIFY(JUCE_MAJOR_VERSION)
  89. "." JUCE_STRINGIFY(JUCE_MINOR_VERSION)
  90. "." JUCE_STRINGIFY(JUCE_BUILDNUMBER)
  91. && version.containsChar ('.')
  92. && version.length() > 2;
  93. }
  94. String version;
  95. URL url;
  96. };
  97. void askUserAboutNewVersion (const VersionInfo& info)
  98. {
  99. if (info.isDifferentVersionToCurrent())
  100. {
  101. File appParentFolder (File::getSpecialLocation (File::currentApplicationFile).getParentDirectory());
  102. if (isZipFolder (appParentFolder))
  103. {
  104. int result = AlertWindow::showYesNoCancelBox (AlertWindow::InfoIcon,
  105. TRANS("Download JUCE version 123?").replace ("123", info.version),
  106. TRANS("A new version of JUCE is available - would you like to overwrite the folder:\n\n"
  107. "xfldrx\n\n"
  108. " ..with the latest version from juce.com?\n\n"
  109. "(Please note that this will overwrite everything in that folder!)")
  110. .replace ("xfldrx", appParentFolder.getFullPathName()),
  111. TRANS("Overwrite"),
  112. TRANS("Choose another folder..."),
  113. TRANS("Cancel"));
  114. if (result == 1)
  115. DownloadNewVersionThread::performDownload (info.url, appParentFolder);
  116. if (result == 2)
  117. askUserForLocationToDownload (info);
  118. }
  119. else
  120. {
  121. if (AlertWindow::showOkCancelBox (AlertWindow::InfoIcon,
  122. TRANS("Download JUCE version 123?").replace ("123", info.version),
  123. TRANS("A new version of JUCE is available - would you like to download it?")))
  124. askUserForLocationToDownload (info);
  125. }
  126. }
  127. }
  128. void askUserForLocationToDownload (const VersionInfo& info)
  129. {
  130. File targetFolder (findDefaultModulesFolder());
  131. if (isJuceModulesFolder (targetFolder))
  132. targetFolder = targetFolder.getParentDirectory();
  133. FileChooser chooser (TRANS("Please select the location into which you'd like to install the new version"),
  134. targetFolder);
  135. if (chooser.browseForDirectory())
  136. {
  137. targetFolder = chooser.getResult();
  138. if (isJuceModulesFolder (targetFolder))
  139. targetFolder = targetFolder.getParentDirectory();
  140. if (targetFolder.getChildFile ("JUCE").isDirectory())
  141. targetFolder = targetFolder.getChildFile ("JUCE");
  142. if (targetFolder.getChildFile (".git").isDirectory())
  143. {
  144. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  145. TRANS ("Downloading new JUCE version"),
  146. TRANS ("This folder is a GIT repository!\n\n"
  147. "You should use a \"git pull\" to update it to the latest version. "
  148. "Or to use the Introjucer to get an update, you should select an empty "
  149. "folder into which you'd like to download the new code."));
  150. return;
  151. }
  152. if (isJuceFolder (targetFolder))
  153. {
  154. if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
  155. TRANS("Overwrite existing JUCE folder?"),
  156. TRANS("Do you want to overwrite the folder:\n\n"
  157. "xfldrx\n\n"
  158. " ..with the latest version from juce.com?\n\n"
  159. "(Please note that this will overwrite everything in that folder!)")
  160. .replace ("xfldrx", targetFolder.getFullPathName())))
  161. {
  162. return;
  163. }
  164. }
  165. else
  166. {
  167. targetFolder = targetFolder.getChildFile ("JUCE").getNonexistentSibling();
  168. }
  169. DownloadNewVersionThread::performDownload (info.url, targetFolder);
  170. }
  171. }
  172. static bool isZipFolder (const File& f)
  173. {
  174. return f.getChildFile ("modules").isDirectory()
  175. && f.getChildFile ("extras").isDirectory()
  176. && f.getChildFile ("examples").isDirectory()
  177. && ! f.getChildFile (".git").isDirectory();
  178. }
  179. private:
  180. void timerCallback() override
  181. {
  182. stopTimer();
  183. if (hasAttemptedToReadWebsite)
  184. processResult (jsonReply);
  185. else
  186. startThread (3);
  187. }
  188. void run() override
  189. {
  190. checkForNewVersion();
  191. }
  192. var jsonReply;
  193. bool hasAttemptedToReadWebsite;
  194. URL newVersionToDownload;
  195. //==============================================================================
  196. class DownloadNewVersionThread : public ThreadWithProgressWindow
  197. {
  198. public:
  199. DownloadNewVersionThread (URL u, File target)
  200. : ThreadWithProgressWindow ("Downloading New Version", true, true),
  201. result (Result::ok()),
  202. url (u), targetFolder (target)
  203. {
  204. }
  205. static void performDownload (URL u, File targetFolder)
  206. {
  207. DownloadNewVersionThread d (u, targetFolder);
  208. if (d.runThread())
  209. {
  210. if (d.result.failed())
  211. {
  212. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  213. "Installation Failed",
  214. d.result.getErrorMessage());
  215. }
  216. else
  217. {
  218. new RelaunchTimer (targetFolder);
  219. }
  220. }
  221. }
  222. void run() override
  223. {
  224. setProgress (-1.0);
  225. MemoryBlock zipData;
  226. result = download (zipData);
  227. if (result.wasOk() && ! threadShouldExit())
  228. result = unzip (zipData);
  229. }
  230. Result download (MemoryBlock& dest)
  231. {
  232. setStatusMessage ("Downloading...");
  233. const ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String::empty, 10000));
  234. if (in != nullptr)
  235. {
  236. int64 total = 0;
  237. MemoryOutputStream mo (dest, true);
  238. for (;;)
  239. {
  240. if (threadShouldExit())
  241. return Result::fail ("cancel");
  242. int64 written = mo.writeFromInputStream (*in, 8192);
  243. if (written == 0)
  244. break;
  245. total += written;
  246. setStatusMessage (String (TRANS ("Downloading... (123)"))
  247. .replace ("123", File::descriptionOfSizeInBytes (total)));
  248. }
  249. return Result::ok();
  250. }
  251. return Result::fail ("Failed to download from: " + url.toString (false));
  252. }
  253. Result unzip (const MemoryBlock& data)
  254. {
  255. setStatusMessage ("Installing...");
  256. File unzipTarget;
  257. bool isUsingTempFolder = false;
  258. {
  259. MemoryInputStream input (data, false);
  260. ZipFile zip (input);
  261. if (zip.getNumEntries() == 0)
  262. return Result::fail ("The downloaded file wasn't a valid JUCE file!");
  263. unzipTarget = targetFolder;
  264. if (unzipTarget.exists())
  265. {
  266. isUsingTempFolder = true;
  267. unzipTarget = targetFolder.getNonexistentSibling();
  268. if (! unzipTarget.createDirectory())
  269. return Result::fail ("Couldn't create a folder to unzip the new version!");
  270. }
  271. Result r (zip.uncompressTo (unzipTarget));
  272. if (r.failed())
  273. {
  274. if (isUsingTempFolder)
  275. unzipTarget.deleteRecursively();
  276. return r;
  277. }
  278. }
  279. if (isUsingTempFolder)
  280. {
  281. File oldFolder (targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling());
  282. if (! targetFolder.moveFileTo (oldFolder))
  283. {
  284. unzipTarget.deleteRecursively();
  285. return Result::fail ("Could not remove the existing folder!");
  286. }
  287. if (! unzipTarget.moveFileTo (targetFolder))
  288. {
  289. unzipTarget.deleteRecursively();
  290. return Result::fail ("Could not overwrite the existing folder!");
  291. }
  292. }
  293. return Result::ok();
  294. }
  295. Result result;
  296. URL url;
  297. File targetFolder;
  298. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadNewVersionThread)
  299. };
  300. struct RelaunchTimer : private Timer
  301. {
  302. RelaunchTimer (const File& f) : parentFolder (f)
  303. {
  304. startTimer (1500);
  305. }
  306. void timerCallback() override
  307. {
  308. stopTimer();
  309. File app = parentFolder.getChildFile (
  310. #if JUCE_MAC
  311. "Introjucer.app");
  312. #elif JUCE_WINDOWS
  313. "Introjucer.exe");
  314. #elif JUCE_LINUX
  315. "Introjucer");
  316. #endif
  317. JUCEApplication::quit();
  318. if (app.exists())
  319. {
  320. app.setExecutePermission (true);
  321. #if JUCE_MAC
  322. app.getChildFile("Contents")
  323. .getChildFile("MacOS")
  324. .getChildFile("Introjucer").setExecutePermission (true);
  325. #endif
  326. app.startAsProcess();
  327. }
  328. delete this;
  329. }
  330. File parentFolder;
  331. };
  332. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LatestVersionChecker)
  333. };
  334. #endif // JUCER_AUTOUPDATER_H_INCLUDED