PageRenderTime 66ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Updates/CheckingState.cs

https://bitbucket.org/tcz001/openpdn
C# | 578 lines | 415 code | 84 blank | 79 comment | 81 complexity | 4c425df6173aa86328ed27aa8b5a6dd7 MD5 | raw file
Possible License(s): Unlicense
  1. /////////////////////////////////////////////////////////////////////////////////
  2. // Paint.NET //
  3. // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
  4. // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
  5. // See src/Resources/Files/License.txt for full licensing and attribution //
  6. // details. //
  7. // . //
  8. /////////////////////////////////////////////////////////////////////////////////
  9. using PaintDotNet.SystemLayer;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Collections.Specialized;
  13. using System.Globalization;
  14. using System.IO;
  15. using System.Net;
  16. using System.Text;
  17. using System.Threading;
  18. using System.Windows.Forms;
  19. namespace PaintDotNet.Updates
  20. {
  21. internal class CheckingState
  22. : UpdatesState
  23. {
  24. // versions.txt schema:
  25. // ; This is a comment
  26. // DownloadPageUrl=downloadPageUrl // This should link to the main download page
  27. // StableVersions=version1,version2,...,versionN // A comma-separated list of all available stable versions available for download
  28. // BetaVersions=version1,version2,...,versionN // A comma-separated list of all available beta/pre-release versions available for download
  29. //
  30. // version1_Name=name1 // Friendly name for a given version
  31. // version1_NetFxVersion=netFxVersion1 // What version of .NET does this version require?
  32. // For .NET 2.0, this should be specificed as 2.0.x, where x used to be the build number (50727) but is now ignored
  33. // For .NET 3.5, this should be specificed as 3.5.x, where x is the required service pack level
  34. // version1_InfoUrl=infoUrl1 // A URL that contains information about the given version
  35. // version1_ZipUrlList="zipUrl1","zipUrl2",...,"zipUrlN"
  36. // // A comma-delimited list of URL's for mirrors to download the updater. One will be chosen at random.
  37. // version1_FullZipUrlList="zipFullUrl1","zipFullUrl2",...,"zipFullUrlN"
  38. // // A comma-delimited list of URL's for mirrors to download the 'full' installer ('full' means it bundles the appropriate .NET installer
  39. // ...
  40. // versionN_Name=name1 // Friendly name for a given version
  41. // versionN_NetFxVersion=netFxVersionN // What version of .NET does this version require?
  42. // versionN_InfoUrl=infoUrlN // A URL that contains information about the given version
  43. // versionN_ZipUrlList=zipUrlN // A comma-delimited list of URL's for mirrors to download the updater. One will be chosen at random.
  44. // versionN_FullZipUrl=zipFullUrlN // A comma-delimited list of URL's for mirrors to download the 'full' installer ('full' means it bundles the appropriate .NET installer
  45. //
  46. // Example:
  47. // ; Paint.NET versions download manifest
  48. // DownloadPageUrl=http://www.getpaint.net/download.htm
  49. // StableVersions=2.1.1958.27164
  50. // BetaVersions=2.5.2013.31044
  51. //
  52. // 2.1.1958.27164_Name=Paint.NET v2.1b
  53. // 2.1.1958.27164_InfoUrl=http://www.getpaint.net/roadmap.htm#v2_1
  54. // 2.1.1958.27164_NetFxVersion=1.1.4322
  55. // 2.1.1958.27164_ZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_1b.zip
  56. // 2.1.1958.27164_FullZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_1b_Full.zip
  57. //
  58. // 2.5.2013.31044_Name=Paint.NET v2.5
  59. // 2.5.2013.31044_InfoUrl=http://www.getpaint.net/roadmap.htm#v2_5
  60. // 2.5.2013.31044_NetFxVersion=1.1.4322
  61. // 2.5.2013.31044_ZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_5.zip
  62. // 2.5.2013.31044_FullZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_5_Full.zip
  63. //
  64. // 2.6.2113.23752_Name=Paint.NET v2.6 Beta 1
  65. // 2.6.2113.23752_InfoUrl=http://www.getpaint.net/roadmap.htm#v2_6
  66. // 2.6.2113.23752_NetFxVersion=2.0.50727
  67. // 2.6.2113.23752_ZipUrlList="http://www.getpaint.net/zip/PaintDotNet_2_6_Beta1.zip","http://www.someotherhost.com/files/PaintDotNet_2_6_Beta1.zip"
  68. // 2.6.2113.23752_FullZipUrlList="http://www.getpaint.net/zip/PaintDotNet_2_6_Beta1_Full.zip","http://www.someotherhost.com/files/PaintDotNet_2_6_Beta1_Full.zip"
  69. //
  70. // Notes:
  71. // A line may have a comment on it. Just start the line with an asterisk, '*'
  72. // Versions must be formatted in a manner parseable by the System.Version class.
  73. // BetaVersions may be an empty list: "BetaVersions="
  74. // versionN_InfoUrl may not be blank
  75. // versionN_ZipUrl may not be blank
  76. // versionN_ZipUrlSize must be greater than 0.
  77. // If any error is detected while parsing, the entire schema will be declared as invalid and ignored.
  78. // Everything is case-sensitive.
  79. private const string downloadPageUrlName = "DownloadPageUrl";
  80. private const string stableVersionsName = "StableVersions";
  81. private const string betaVersionsName = "BetaVersions";
  82. private const string nameNameFormat = "{0}_Name";
  83. private const string netFxVersionNameFormat = "{0}_NetFxVersion";
  84. private const string infoUrlNameFormat = "{0}_InfoUrl";
  85. private const string zipUrlListNameFormat = "{0}_ZipUrlList";
  86. private const string fullZipUrlListNameFormat = "{0}_FullZipUrlList";
  87. private const char commentChar = ';';
  88. // {0} is schema version
  89. // {1} is Windows revision (501 for XP, 502 for Server 2k3, 600 for Vista, 601 for Win7)
  90. // {2} is platform (x86, x64)
  91. // {3} is the locale (en, etc)
  92. private const string versionManifestRelativeUrlFormat = "/updates/versions.{0}.{1}.{2}.{3}.txt";
  93. private const string versionManifestTestRelativeUrl = "/updates/versions.txt.test.txt";
  94. private const int schemaVersion = 5;
  95. private PdnVersionManifest manifest;
  96. private int latestVersionIndex;
  97. private Exception exception;
  98. private ManualResetEvent checkingEvent = new ManualResetEvent(false);
  99. private ManualResetEvent abortEvent = new ManualResetEvent(false);
  100. private static string GetNeutralLocaleName(CultureInfo ci)
  101. {
  102. if (ci.IsNeutralCulture)
  103. {
  104. return ci.Name;
  105. }
  106. if (ci.Parent == null)
  107. {
  108. return ci.Name;
  109. }
  110. if (ci.Parent == ci)
  111. {
  112. return ci.Name;
  113. }
  114. return GetNeutralLocaleName(ci.Parent);
  115. }
  116. private static string VersionManifestUrl
  117. {
  118. get
  119. {
  120. Uri websiteUri = new Uri(InvariantStrings.WebsiteUrl);
  121. string versionManifestUrl;
  122. if (PdnInfo.IsTestMode)
  123. {
  124. Uri versionManifestTestUri = new Uri(websiteUri, versionManifestTestRelativeUrl);
  125. versionManifestUrl = versionManifestTestUri.ToString();
  126. }
  127. else
  128. {
  129. string schemaVersionStr = schemaVersion.ToString(CultureInfo.InvariantCulture);
  130. Version osVersion = Environment.OSVersion.Version;
  131. ProcessorArchitecture platform = SystemLayer.Processor.Architecture;
  132. OSType osType = SystemLayer.OS.Type;
  133. // If this is XP x64, we want to fudge the NT version to be 5.1 instead of 5.2
  134. // This helps us discern between XP x64 and Server 2003 x64 stats.
  135. if (osVersion.Major == 5 && osVersion.Minor == 2 && platform == ProcessorArchitecture.X64 && osType == OSType.Workstation)
  136. {
  137. osVersion = new Version(5, 1, osVersion.Build, osVersion.Revision);
  138. }
  139. int osVersionInt = (osVersion.Major * 100) + osVersion.Minor;
  140. string osVersionStr = osVersionInt.ToString(CultureInfo.InvariantCulture);
  141. string platformStr = platform.ToString().ToLower();
  142. string localeStr = GetNeutralLocaleName(PdnResources.Culture);
  143. Uri versionManifestUrlFormatUri = new Uri(websiteUri, versionManifestRelativeUrlFormat);
  144. string versionManifestUrlFormat = versionManifestUrlFormatUri.ToString();
  145. versionManifestUrl = string.Format(versionManifestUrlFormat, schemaVersionStr, osVersionStr, platformStr, localeStr);
  146. }
  147. return versionManifestUrl;
  148. }
  149. }
  150. private static string[] BreakIntoLines(string text)
  151. {
  152. StringReader sr = new StringReader(text);
  153. List<string> strings = new List<string>();
  154. string line;
  155. while ((line = sr.ReadLine()) != null)
  156. {
  157. if (line.Length > 0 && line[0] != commentChar)
  158. {
  159. strings.Add(line);
  160. }
  161. }
  162. return strings.ToArray();
  163. }
  164. private static void LineToNameValue(string line, out string name, out string value)
  165. {
  166. int equalIndex = line.IndexOf('=');
  167. if (equalIndex == -1)
  168. {
  169. throw new FormatException("Line had no equal sign (=) present");
  170. }
  171. name = line.Substring(0, equalIndex);
  172. int valueLength = line.Length - equalIndex - 1;
  173. if (valueLength == 0)
  174. {
  175. value = string.Empty;
  176. }
  177. else
  178. {
  179. value = line.Substring(equalIndex + 1, line.Length - equalIndex - 1);
  180. }
  181. }
  182. private static NameValueCollection LinesToNameValues(string[] lines)
  183. {
  184. NameValueCollection nvc = new NameValueCollection();
  185. foreach (string line in lines)
  186. {
  187. string name;
  188. string value;
  189. LineToNameValue(line, out name, out value);
  190. nvc.Add(name, value);
  191. }
  192. return nvc;
  193. }
  194. private static Version[] VersionStringToArray(string versions)
  195. {
  196. string[] versionStrings = versions.Split(',');
  197. // For the 'null' case...
  198. if (versionStrings.Length == 0 ||
  199. (versionStrings.Length == 1 && versionStrings[0].Length == 0))
  200. {
  201. return new Version[0];
  202. }
  203. Version[] versionList = new Version[versionStrings.Length];
  204. for (int i = 0; i < versionStrings.Length; ++i)
  205. {
  206. versionList[i] = new Version(versionStrings[i]);
  207. }
  208. return versionList;
  209. }
  210. private static string[] BuildVersionValueMapping(NameValueCollection nameValues, Version[] versions, string secondaryKeyFormat)
  211. {
  212. string[] newValues = new string[versions.Length];
  213. for (int i = 0; i < versions.Length; ++i)
  214. {
  215. string versionString = versions[i].ToString();
  216. string secondaryKey = string.Format(secondaryKeyFormat, versionString);
  217. string secondaryValue = nameValues[secondaryKey];
  218. newValues[i] = secondaryValue;
  219. }
  220. return newValues;
  221. }
  222. private static void SplitUrlList(string urlList, List<string> urlsOutput)
  223. {
  224. if (string.IsNullOrEmpty(urlList))
  225. {
  226. return;
  227. }
  228. string trimUrlList = urlList.Trim();
  229. string url;
  230. int commaIndex;
  231. if (trimUrlList[0] == '"')
  232. {
  233. int endQuoteIndex = trimUrlList.IndexOf('"', 1);
  234. commaIndex = trimUrlList.IndexOf(',', endQuoteIndex);
  235. url = trimUrlList.Substring(1, endQuoteIndex - 1);
  236. }
  237. else
  238. {
  239. commaIndex = trimUrlList.IndexOf(',');
  240. if (commaIndex == -1)
  241. {
  242. url = trimUrlList;
  243. }
  244. else
  245. {
  246. url = trimUrlList.Substring(0, commaIndex);
  247. }
  248. }
  249. string urlTail;
  250. if (commaIndex == -1)
  251. {
  252. urlTail = null;
  253. }
  254. else
  255. {
  256. urlTail = trimUrlList.Substring(commaIndex + 1);
  257. }
  258. urlsOutput.Add(url);
  259. SplitUrlList(urlTail, urlsOutput);
  260. }
  261. /// <summary>
  262. /// Downloads the latest updates manifest from the Paint.NET web server.
  263. /// </summary>
  264. /// <returns>The latest updates manifest, or null if there was an error in which case the exception argument will be non-null.</returns>
  265. private static PdnVersionManifest GetUpdatesManifest(out Exception exception)
  266. {
  267. try
  268. {
  269. string versionsUrl = VersionManifestUrl;
  270. Uri versionsUri = new Uri(versionsUrl);
  271. byte[] manifestBuffer = Utility.DownloadSmallFile(versionsUri);
  272. string manifestText = System.Text.Encoding.UTF8.GetString(manifestBuffer);
  273. string[] manifestLines = BreakIntoLines(manifestText);
  274. NameValueCollection nameValues = LinesToNameValues(manifestLines);
  275. string downloadPageUrl = nameValues[downloadPageUrlName];
  276. string stableVersionsStrings = nameValues[stableVersionsName];
  277. Version[] stableVersions = VersionStringToArray(stableVersionsStrings);
  278. string[] stableNames = BuildVersionValueMapping(nameValues, stableVersions, nameNameFormat);
  279. string[] stableNetFxVersions = BuildVersionValueMapping(nameValues, stableVersions, netFxVersionNameFormat);
  280. string[] stableInfoUrls = BuildVersionValueMapping(nameValues, stableVersions, infoUrlNameFormat);
  281. string[] stableZipUrls = BuildVersionValueMapping(nameValues, stableVersions, zipUrlListNameFormat);
  282. string[] stableFullZipUrls = BuildVersionValueMapping(nameValues, stableVersions, fullZipUrlListNameFormat);
  283. string betaVersionsStrings = nameValues[betaVersionsName];
  284. Version[] betaVersions = VersionStringToArray(betaVersionsStrings);
  285. string[] betaNames = BuildVersionValueMapping(nameValues, betaVersions, nameNameFormat);
  286. string[] betaNetFxVersions = BuildVersionValueMapping(nameValues, betaVersions, netFxVersionNameFormat);
  287. string[] betaInfoUrls = BuildVersionValueMapping(nameValues, betaVersions, infoUrlNameFormat);
  288. string[] betaZipUrls = BuildVersionValueMapping(nameValues, betaVersions, zipUrlListNameFormat);
  289. string[] betaFullZipUrls = BuildVersionValueMapping(nameValues, betaVersions, fullZipUrlListNameFormat);
  290. PdnVersionInfo[] versionInfos = new PdnVersionInfo[betaVersions.Length + stableVersions.Length];
  291. int cursor = 0;
  292. for (int i = 0; i < stableVersions.Length; ++i)
  293. {
  294. List<string> zipUrlList = new List<string>();
  295. SplitUrlList(stableZipUrls[i], zipUrlList);
  296. List<string> fullZipUrlList = new List<string>();
  297. SplitUrlList(stableFullZipUrls[i], fullZipUrlList);
  298. Version netFxVersion = new Version(stableNetFxVersions[i]);
  299. if (netFxVersion.Major == 2 && netFxVersion.Minor == 0)
  300. {
  301. netFxVersion = new Version(2, 0, 0); // discard the build # that is specified, since we use that for Service Pack level now
  302. }
  303. PdnVersionInfo info = new PdnVersionInfo(
  304. stableVersions[i],
  305. stableNames[i],
  306. netFxVersion.Major,
  307. netFxVersion.Minor,
  308. netFxVersion.Build, // service pack
  309. stableInfoUrls[i],
  310. zipUrlList.ToArray(),
  311. fullZipUrlList.ToArray(),
  312. true);
  313. versionInfos[cursor] = info;
  314. ++cursor;
  315. }
  316. for (int i = 0; i < betaVersions.Length; ++i)
  317. {
  318. List<string> zipUrlList = new List<string>();
  319. SplitUrlList(betaZipUrls[i], zipUrlList);
  320. List<string> fullZipUrlList = new List<string>();
  321. SplitUrlList(betaFullZipUrls[i], fullZipUrlList);
  322. Version netFxVersion = new Version(betaNetFxVersions[i]);
  323. if (netFxVersion.Major == 2 && netFxVersion.Minor == 0)
  324. {
  325. netFxVersion = new Version(2, 0, 0); // discard the build # that is specified, since we use that for Service Pack level now
  326. }
  327. PdnVersionInfo info = new PdnVersionInfo(
  328. betaVersions[i],
  329. betaNames[i],
  330. netFxVersion.Major,
  331. netFxVersion.Minor,
  332. netFxVersion.Build, // service pack
  333. betaInfoUrls[i],
  334. zipUrlList.ToArray(),
  335. fullZipUrlList.ToArray(),
  336. false);
  337. versionInfos[cursor] = info;
  338. ++cursor;
  339. }
  340. PdnVersionManifest manifest = new PdnVersionManifest(downloadPageUrl, versionInfos);
  341. exception = null;
  342. return manifest;
  343. }
  344. catch (Exception ex)
  345. {
  346. exception = ex;
  347. return null;
  348. }
  349. }
  350. private static void CheckForUpdates(
  351. out PdnVersionManifest manifestResult,
  352. out int latestVersionIndexResult,
  353. out Exception exception)
  354. {
  355. exception = null;
  356. PdnVersionManifest manifest = null;
  357. manifestResult = null;
  358. latestVersionIndexResult = -1;
  359. int retries = 2;
  360. while (retries > 0)
  361. {
  362. try
  363. {
  364. manifest = GetUpdatesManifest(out exception);
  365. retries = 0;
  366. }
  367. catch (Exception ex)
  368. {
  369. exception = ex;
  370. --retries;
  371. if (retries == 0)
  372. {
  373. manifest = null;
  374. }
  375. }
  376. }
  377. if (manifest != null)
  378. {
  379. int stableIndex = manifest.GetLatestStableVersionIndex();
  380. int betaIndex = manifest.GetLatestBetaVersionIndex();
  381. // Check for betas as well?
  382. bool checkForBetas = ("1" == Settings.SystemWide.GetString(SettingNames.AlsoCheckForBetas, "0"));
  383. // Figure out which version we want to compare against the current version
  384. int latestIndex = stableIndex;
  385. if (checkForBetas)
  386. {
  387. // If they like betas, and if the beta is newer than the latest stable release,
  388. // then offer it to them.
  389. if (betaIndex != -1 &&
  390. (stableIndex == -1 || manifest.VersionInfos[betaIndex].Version >= manifest.VersionInfos[stableIndex].Version))
  391. {
  392. latestIndex = betaIndex;
  393. }
  394. }
  395. // Now compare that version against the current version
  396. if (latestIndex != -1)
  397. {
  398. if (PdnInfo.IsTestMode ||
  399. manifest.VersionInfos[latestIndex].Version > PdnInfo.GetVersion())
  400. {
  401. manifestResult = manifest;
  402. latestVersionIndexResult = latestIndex;
  403. }
  404. }
  405. }
  406. }
  407. public override bool CanAbort
  408. {
  409. get
  410. {
  411. return true;
  412. }
  413. }
  414. protected override void OnAbort()
  415. {
  416. this.abortEvent.Set();
  417. base.OnAbort();
  418. }
  419. private void DoCheckThreadProc(object ignored)
  420. {
  421. try
  422. {
  423. System.Threading.Thread.Sleep(1500);
  424. CheckForUpdates(out this.manifest, out this.latestVersionIndex, out this.exception);
  425. }
  426. finally
  427. {
  428. this.checkingEvent.Set();
  429. }
  430. }
  431. public override void OnEnteredState()
  432. {
  433. this.checkingEvent.Reset();
  434. this.abortEvent.Reset();
  435. ThreadPool.QueueUserWorkItem(new WaitCallback(DoCheckThreadProc));
  436. WaitHandleArray events = new WaitHandleArray(2);
  437. events[0] = this.checkingEvent;
  438. events[1] = this.abortEvent;
  439. int waitResult = events.WaitAny();
  440. if (waitResult == 0 && manifest != null && latestVersionIndex != -1)
  441. {
  442. StateMachine.QueueInput(PrivateInput.GoToUpdateAvailable);
  443. }
  444. else if (waitResult == 1)
  445. {
  446. StateMachine.QueueInput(PrivateInput.GoToAborted);
  447. }
  448. else if (this.exception != null)
  449. {
  450. StateMachine.QueueInput(PrivateInput.GoToError);
  451. }
  452. else
  453. {
  454. StateMachine.QueueInput(PrivateInput.GoToDone);
  455. }
  456. }
  457. public override void ProcessInput(object input, out State newState)
  458. {
  459. if (input.Equals(PrivateInput.GoToUpdateAvailable))
  460. {
  461. newState = new UpdateAvailableState(this.manifest.VersionInfos[this.latestVersionIndex]);
  462. }
  463. else if (input.Equals(PrivateInput.GoToError))
  464. {
  465. string errorMessage;
  466. if (this.exception is WebException)
  467. {
  468. errorMessage = Utility.WebExceptionToErrorMessage((WebException)this.exception);
  469. }
  470. else
  471. {
  472. errorMessage = PdnResources.GetString("Updates.CheckingState.GenericError");
  473. }
  474. newState = new ErrorState(this.exception, errorMessage);
  475. }
  476. else if (input.Equals(PrivateInput.GoToDone))
  477. {
  478. newState = new DoneState();
  479. }
  480. else if (input.Equals(PrivateInput.GoToAborted))
  481. {
  482. newState = new AbortedState();
  483. }
  484. else
  485. {
  486. throw new ArgumentException();
  487. }
  488. }
  489. public CheckingState()
  490. : base(false, false, MarqueeStyle.Marquee)
  491. {
  492. }
  493. }
  494. }