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.

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