PageRenderTime 55ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Updates/CheckingState.cs

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