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.

425 lines
15KB

  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. if (isRunningFromZipFolder())
  102. {
  103. if (AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
  104. TRANS("Download JUCE version 123?").replace ("123", info.version),
  105. TRANS("A new version of JUCE is available - would you like to overwrite the folder:\n\n"
  106. "xfldrx\n\n"
  107. " ..with the latest version from juce.com?\n\n"
  108. "(Please note that this will overwrite everything in that folder!)")
  109. .replace ("xfldrx", getZipFolder().getFullPathName())))
  110. {
  111. DownloadNewVersionThread::performDownload (info.url, getZipFolder());
  112. }
  113. }
  114. else
  115. {
  116. if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
  117. TRANS("Download JUCE version 123?").replace ("123", info.version),
  118. TRANS("A new version of JUCE is available - would you like to download it?")))
  119. {
  120. return;
  121. }
  122. File targetFolder (findDefaultModulesFolder());
  123. if (isJuceModulesFolder (targetFolder))
  124. targetFolder = targetFolder.getParentDirectory();
  125. FileChooser chooser (TRANS("Please select the location into which you'd like to install the new version"),
  126. targetFolder);
  127. if (chooser.browseForDirectory())
  128. {
  129. targetFolder = chooser.getResult();
  130. if (isJuceModulesFolder (targetFolder))
  131. targetFolder = targetFolder.getParentDirectory();
  132. if (targetFolder.getChildFile ("JUCE").isDirectory())
  133. targetFolder = targetFolder.getChildFile ("JUCE");
  134. if (targetFolder.getChildFile (".git").isDirectory())
  135. {
  136. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  137. TRANS ("Downloading new JUCE version"),
  138. TRANS ("This folder is a GIT repository!\n\n"
  139. "You should use a \"git pull\" to update it to the latest version. "
  140. "Or to use the Introjucer to get an update, you should select an empty "
  141. "folder into which you'd like to download the new code."));
  142. return;
  143. }
  144. if (isJuceFolder (targetFolder))
  145. {
  146. if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
  147. TRANS("Overwrite existing JUCE folder?"),
  148. TRANS("Do you want to overwrite the folder:\n\n"
  149. "xfldrx\n\n"
  150. " ..with the latest version from juce.com?\n\n"
  151. "(Please note that this will overwrite everything in that folder!)")
  152. .replace ("xfldrx", targetFolder.getFullPathName())))
  153. {
  154. return;
  155. }
  156. }
  157. else
  158. {
  159. targetFolder = targetFolder.getChildFile ("JUCE").getNonexistentSibling();
  160. }
  161. DownloadNewVersionThread::performDownload (info.url, targetFolder);
  162. }
  163. }
  164. }
  165. }
  166. static bool isZipFolder (const File& f)
  167. {
  168. return f.getChildFile ("modules").isDirectory()
  169. && f.getChildFile ("extras").isDirectory()
  170. && f.getChildFile ("examples").isDirectory()
  171. && ! f.getChildFile (".git").isDirectory();
  172. }
  173. static File getZipFolder()
  174. {
  175. File appParentFolder (File::getSpecialLocation (File::currentApplicationFile).getParentDirectory());
  176. return isZipFolder (appParentFolder) ? appParentFolder : File::nonexistent;
  177. }
  178. static bool isRunningFromZipFolder()
  179. {
  180. return getZipFolder() != File::nonexistent;
  181. }
  182. private:
  183. void timerCallback() override
  184. {
  185. stopTimer();
  186. if (hasAttemptedToReadWebsite)
  187. processResult (jsonReply);
  188. else
  189. startThread (3);
  190. }
  191. void run() override
  192. {
  193. checkForNewVersion();
  194. }
  195. var jsonReply;
  196. bool hasAttemptedToReadWebsite;
  197. URL newVersionToDownload;
  198. //==============================================================================
  199. class DownloadNewVersionThread : public ThreadWithProgressWindow
  200. {
  201. public:
  202. DownloadNewVersionThread (URL u, File target)
  203. : ThreadWithProgressWindow ("Downloading New Version", true, true),
  204. result (Result::ok()),
  205. url (u), targetFolder (target)
  206. {
  207. }
  208. static void performDownload (URL u, File targetFolder)
  209. {
  210. DownloadNewVersionThread d (u, targetFolder);
  211. if (d.runThread())
  212. {
  213. if (d.result.failed())
  214. {
  215. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  216. "Installation Failed",
  217. d.result.getErrorMessage());
  218. }
  219. else
  220. {
  221. new RelaunchTimer (targetFolder);
  222. }
  223. }
  224. }
  225. void run() override
  226. {
  227. setProgress (-1.0);
  228. MemoryBlock zipData;
  229. result = download (zipData);
  230. if (result.wasOk() && ! threadShouldExit())
  231. result = unzip (zipData);
  232. }
  233. Result download (MemoryBlock& dest)
  234. {
  235. setStatusMessage ("Downloading...");
  236. const ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String::empty, 10000));
  237. if (in != nullptr)
  238. {
  239. int64 total = 0;
  240. MemoryOutputStream mo (dest, true);
  241. for (;;)
  242. {
  243. if (threadShouldExit())
  244. return Result::fail ("cancel");
  245. int64 written = mo.writeFromInputStream (*in, 8192);
  246. if (written == 0)
  247. break;
  248. total += written;
  249. setStatusMessage (String (TRANS ("Downloading... (123)"))
  250. .replace ("123", File::descriptionOfSizeInBytes (total)));
  251. }
  252. return Result::ok();
  253. }
  254. return Result::fail ("Failed to download from: " + url.toString (false));
  255. }
  256. Result unzip (const MemoryBlock& data)
  257. {
  258. setStatusMessage ("Installing...");
  259. File unzipTarget;
  260. bool isUsingTempFolder = false;
  261. {
  262. MemoryInputStream input (data, false);
  263. ZipFile zip (input);
  264. if (zip.getNumEntries() == 0)
  265. return Result::fail ("The downloaded file wasn't a valid JUCE file!");
  266. unzipTarget = targetFolder;
  267. if (unzipTarget.exists())
  268. {
  269. isUsingTempFolder = true;
  270. unzipTarget = targetFolder.getNonexistentSibling();
  271. if (! unzipTarget.createDirectory())
  272. return Result::fail ("Couldn't create a folder to unzip the new version!");
  273. }
  274. Result r (zip.uncompressTo (unzipTarget));
  275. if (r.failed())
  276. {
  277. if (isUsingTempFolder)
  278. unzipTarget.deleteRecursively();
  279. return r;
  280. }
  281. }
  282. if (isUsingTempFolder)
  283. {
  284. File oldFolder (targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling());
  285. if (! targetFolder.moveFileTo (oldFolder))
  286. {
  287. unzipTarget.deleteRecursively();
  288. return Result::fail ("Could not remove the existing folder!");
  289. }
  290. if (! unzipTarget.moveFileTo (targetFolder))
  291. {
  292. unzipTarget.deleteRecursively();
  293. return Result::fail ("Could not overwrite the existing folder!");
  294. }
  295. }
  296. return Result::ok();
  297. }
  298. Result result;
  299. URL url;
  300. File targetFolder;
  301. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadNewVersionThread)
  302. };
  303. struct RelaunchTimer : private Timer
  304. {
  305. RelaunchTimer (const File& f) : parentFolder (f)
  306. {
  307. startTimer (1500);
  308. }
  309. void timerCallback() override
  310. {
  311. stopTimer();
  312. File app = parentFolder.getChildFile (
  313. #if JUCE_MAC
  314. "Introjucer.app");
  315. #elif JUCE_WINDOWS
  316. "Introjucer.exe");
  317. #elif JUCE_LINUX
  318. "Introjucer");
  319. #endif
  320. JUCEApplication::quit();
  321. if (app.exists())
  322. {
  323. app.setExecutePermission (true);
  324. #if JUCE_MAC
  325. app.getChildFile("Contents")
  326. .getChildFile("MacOS")
  327. .getChildFile("Introjucer").setExecutePermission (true);
  328. #endif
  329. app.startAsProcess();
  330. }
  331. delete this;
  332. }
  333. File parentFolder;
  334. };
  335. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LatestVersionChecker)
  336. };
  337. #endif // JUCER_AUTOUPDATER_H_INCLUDED