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.

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