/src/Updates/CheckingState.cs
C# | 544 lines | 383 code | 82 blank | 79 comment | 75 complexity | 3b976f8484e6d99ee93e835881f0ee6a MD5 | raw file
- /////////////////////////////////////////////////////////////////////////////////
- // Paint.NET //
- // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
- // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
- // See src/Resources/Files/License.txt for full licensing and attribution //
- // details. //
- // . //
- /////////////////////////////////////////////////////////////////////////////////
-
- using PaintDotNet.SystemLayer;
- using System;
- using System.Collections.Generic;
- using System.Collections.Specialized;
- using System.Globalization;
- using System.IO;
- using System.Net;
- using System.Threading;
-
- namespace PaintDotNet.Updates
- {
- internal class CheckingState
- : UpdatesState
- {
- // versions.txt schema:
- // ; This is a comment
- // DownloadPageUrl=downloadPageUrl // This should link to the main download page
- // StableVersions=version1,version2,...,versionN // A comma-separated list of all available stable versions available for download
- // BetaVersions=version1,version2,...,versionN // A comma-separated list of all available beta/pre-release versions available for download
- //
- // version1_Name=name1 // Friendly name for a given version
- // version1_NetFxVersion=netFxVersion1 // What version of .NET does this version require?
- // 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
- // For .NET 3.5, this should be specificed as 3.5.x, where x is the required service pack level
- // version1_InfoUrl=infoUrl1 // A URL that contains information about the given version
- // version1_ZipUrlList="zipUrl1","zipUrl2",...,"zipUrlN"
- // // A comma-delimited list of URL's for mirrors to download the updater. One will be chosen at random.
- // version1_FullZipUrlList="zipFullUrl1","zipFullUrl2",...,"zipFullUrlN"
- // // A comma-delimited list of URL's for mirrors to download the 'full' installer ('full' means it bundles the appropriate .NET installer
- // ...
- // versionN_Name=name1 // Friendly name for a given version
- // versionN_NetFxVersion=netFxVersionN // What version of .NET does this version require?
- // versionN_InfoUrl=infoUrlN // A URL that contains information about the given version
- // versionN_ZipUrlList=zipUrlN // A comma-delimited list of URL's for mirrors to download the updater. One will be chosen at random.
- // 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
- //
- // Example:
- // ; Paint.NET versions download manifest
- // DownloadPageUrl=http://www.getpaint.net/download.htm
- // StableVersions=2.1.1958.27164
- // BetaVersions=2.5.2013.31044
- //
- // 2.1.1958.27164_Name=Paint.NET v2.1b
- // 2.1.1958.27164_InfoUrl=http://www.getpaint.net/roadmap.htm#v2_1
- // 2.1.1958.27164_NetFxVersion=1.1.4322
- // 2.1.1958.27164_ZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_1b.zip
- // 2.1.1958.27164_FullZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_1b_Full.zip
- //
- // 2.5.2013.31044_Name=Paint.NET v2.5
- // 2.5.2013.31044_InfoUrl=http://www.getpaint.net/roadmap.htm#v2_5
- // 2.5.2013.31044_NetFxVersion=1.1.4322
- // 2.5.2013.31044_ZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_5.zip
- // 2.5.2013.31044_FullZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_5_Full.zip
- //
- // 2.6.2113.23752_Name=Paint.NET v2.6 Beta 1
- // 2.6.2113.23752_InfoUrl=http://www.getpaint.net/roadmap.htm#v2_6
- // 2.6.2113.23752_NetFxVersion=2.0.50727
- // 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"
- // 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"
- //
- // Notes:
- // A line may have a comment on it. Just start the line with an asterisk, '*'
- // Versions must be formatted in a manner parseable by the System.Version class.
- // BetaVersions may be an empty list: "BetaVersions="
- // versionN_InfoUrl may not be blank
- // versionN_ZipUrl may not be blank
- // versionN_ZipUrlSize must be greater than 0.
- // If any error is detected while parsing, the entire schema will be declared as invalid and ignored.
- // Everything is case-sensitive.
-
- private const string DownloadPageUrlName = "DownloadPageUrl";
- private const string StableVersionsName = "StableVersions";
- private const string BetaVersionsName = "BetaVersions";
- private const string NameNameFormat = "{0}_Name";
- private const string NetFxVersionNameFormat = "{0}_NetFxVersion";
- private const string InfoUrlNameFormat = "{0}_InfoUrl";
- private const string ZipUrlListNameFormat = "{0}_ZipUrlList";
- private const string FullZipUrlListNameFormat = "{0}_FullZipUrlList";
- private const char CommentChar = ';';
-
- // {0} is schema version
- // {1} is Windows revision (501 for XP, 502 for Server 2k3, 600 for Vista, 601 for Win7)
- // {2} is platform (x86, x64)
- // {3} is the locale (en, etc)
- private const string VersionManifestRelativeUrlFormat = "/updates/versions.{0}.{1}.{2}.{3}.txt";
- private const string VersionManifestTestRelativeUrl = "/updates/versions.txt.test.txt";
- private const int SchemaVersion = 5;
-
- private PdnVersionManifest _manifest;
- private int _latestVersionIndex;
- private Exception _exception;
-
- private readonly ManualResetEvent _checkingEvent = new ManualResetEvent(false);
- private readonly ManualResetEvent _abortEvent = new ManualResetEvent(false);
-
- private static string GetNeutralLocaleName(CultureInfo ci)
- {
- if (ci.IsNeutralCulture)
- {
- return ci.Name;
- }
-
- return ci.Parent == ci ? ci.Name : GetNeutralLocaleName(ci.Parent);
- }
-
- private static string VersionManifestUrl
- {
- get
- {
- var websiteUri = new Uri(InvariantStrings.WebsiteUrl);
- string versionManifestUrl;
-
- if (PdnInfo.IsTestMode)
- {
- var versionManifestTestUri = new Uri(websiteUri, VersionManifestTestRelativeUrl);
- versionManifestUrl = versionManifestTestUri.ToString();
- }
- else
- {
- string schemaVersionStr = SchemaVersion.ToString(CultureInfo.InvariantCulture);
- Version osVersion = Environment.OSVersion.Version;
- ProcessorArchitecture platform = Processor.Architecture;
- OSType osType = OS.Type;
-
- // If this is XP x64, we want to fudge the NT version to be 5.1 instead of 5.2
- // This helps us discern between XP x64 and Server 2003 x64 stats.
- if (osVersion.Major == 5 && osVersion.Minor == 2 && platform == ProcessorArchitecture.X64 && osType == OSType.Workstation)
- {
- osVersion = new Version(5, 1, osVersion.Build, osVersion.Revision);
- }
-
- int osVersionInt = (osVersion.Major * 100) + osVersion.Minor;
- string osVersionStr = osVersionInt.ToString(CultureInfo.InvariantCulture);
- string platformStr = platform.ToString().ToLower();
- string localeStr = GetNeutralLocaleName(PdnResources.Culture);
- var versionManifestUrlFormatUri = new Uri(websiteUri, VersionManifestRelativeUrlFormat);
- string versionManifestUrlFormat = versionManifestUrlFormatUri.ToString();
-
- versionManifestUrl = string.Format(versionManifestUrlFormat, schemaVersionStr, osVersionStr, platformStr, localeStr);
- }
-
- return versionManifestUrl;
- }
- }
-
- private static IEnumerable<string> BreakIntoLines(string text)
- {
- var sr = new StringReader(text);
- var strings = new List<string>();
- string line;
-
- while ((line = sr.ReadLine()) != null)
- {
- if (line.Length > 0 && line[0] != CommentChar)
- {
- strings.Add(line);
- }
- }
-
- return strings.ToArray();
- }
-
- private static void LineToNameValue(string line, out string name, out string value)
- {
- int equalIndex = line.IndexOf('=');
-
- if (equalIndex == -1)
- {
- throw new FormatException("Line had no equal sign (=) present");
- }
-
- name = line.Substring(0, equalIndex);
-
- int valueLength = line.Length - equalIndex - 1;
-
- value = valueLength == 0 ? string.Empty : line.Substring(equalIndex + 1, line.Length - equalIndex - 1);
- }
-
- private static NameValueCollection LinesToNameValues(IEnumerable<string> lines)
- {
- var nvc = new NameValueCollection();
-
- foreach (string line in lines)
- {
- string name;
- string value;
-
- LineToNameValue(line, out name, out value);
- nvc.Add(name, value);
- }
-
- return nvc;
- }
-
- private static Version[] VersionStringToArray(string versions)
- {
- string[] versionStrings = versions.Split(',');
-
- // For the 'null' case...
- if (versionStrings.Length == 0 ||
- (versionStrings.Length == 1 && versionStrings[0].Length == 0))
- {
- return new Version[0];
- }
-
- var versionList = new Version[versionStrings.Length];
-
- for (int i = 0; i < versionStrings.Length; ++i)
- {
- versionList[i] = new Version(versionStrings[i]);
- }
-
- return versionList;
- }
-
- private static string[] BuildVersionValueMapping(NameValueCollection nameValues, IList<Version> versions, string secondaryKeyFormat)
- {
- var newValues = new string[versions.Count];
-
- for (int i = 0; i < versions.Count; ++i)
- {
- string versionString = versions[i].ToString();
- string secondaryKey = string.Format(secondaryKeyFormat, versionString);
- string secondaryValue = nameValues[secondaryKey];
- newValues[i] = secondaryValue;
- }
-
- return newValues;
- }
-
- private static void SplitUrlList(string urlList, ICollection<string> urlsOutput)
- {
- if (string.IsNullOrEmpty(urlList))
- {
- return;
- }
-
- string trimUrlList = urlList.Trim();
- string url;
- int commaIndex;
-
- if (trimUrlList[0] == '"')
- {
- int endQuoteIndex = trimUrlList.IndexOf('"', 1);
- commaIndex = trimUrlList.IndexOf(',', endQuoteIndex);
- url = trimUrlList.Substring(1, endQuoteIndex - 1);
- }
- else
- {
- commaIndex = trimUrlList.IndexOf(',');
-
- url = commaIndex == -1 ? trimUrlList : trimUrlList.Substring(0, commaIndex);
- }
-
- string urlTail = commaIndex == -1 ? null : trimUrlList.Substring(commaIndex + 1);
-
- urlsOutput.Add(url);
- SplitUrlList(urlTail, urlsOutput);
- }
-
- /// <summary>
- /// Downloads the latest updates manifest from the Paint.NET web server.
- /// </summary>
- /// <returns>The latest updates manifest, or null if there was an error in which case the exception argument will be non-null.</returns>
- private static PdnVersionManifest GetUpdatesManifest(out Exception exception)
- {
- try
- {
- string versionsUrl = VersionManifestUrl;
-
- var versionsUri = new Uri(versionsUrl);
- byte[] manifestBuffer = Utility.DownloadSmallFile(versionsUri);
- string manifestText = System.Text.Encoding.UTF8.GetString(manifestBuffer);
- IEnumerable<string> manifestLines = BreakIntoLines(manifestText);
- NameValueCollection nameValues = LinesToNameValues(manifestLines);
-
- string downloadPageUrl = nameValues[DownloadPageUrlName];
-
- string stableVersionsStrings = nameValues[StableVersionsName];
- Version[] stableVersions = VersionStringToArray(stableVersionsStrings);
- string[] stableNames = BuildVersionValueMapping(nameValues, stableVersions, NameNameFormat);
- string[] stableNetFxVersions = BuildVersionValueMapping(nameValues, stableVersions, NetFxVersionNameFormat);
- string[] stableInfoUrls = BuildVersionValueMapping(nameValues, stableVersions, InfoUrlNameFormat);
- string[] stableZipUrls = BuildVersionValueMapping(nameValues, stableVersions, ZipUrlListNameFormat);
- string[] stableFullZipUrls = BuildVersionValueMapping(nameValues, stableVersions, FullZipUrlListNameFormat);
-
- string betaVersionsStrings = nameValues[BetaVersionsName];
- Version[] betaVersions = VersionStringToArray(betaVersionsStrings);
- string[] betaNames = BuildVersionValueMapping(nameValues, betaVersions, NameNameFormat);
- string[] betaNetFxVersions = BuildVersionValueMapping(nameValues, betaVersions, NetFxVersionNameFormat);
- string[] betaInfoUrls = BuildVersionValueMapping(nameValues, betaVersions, InfoUrlNameFormat);
- string[] betaZipUrls = BuildVersionValueMapping(nameValues, betaVersions, ZipUrlListNameFormat);
- string[] betaFullZipUrls = BuildVersionValueMapping(nameValues, betaVersions, FullZipUrlListNameFormat);
-
- var versionInfos = new PdnVersionInfo[betaVersions.Length + stableVersions.Length];
-
- int cursor = 0;
- for (int i = 0; i < stableVersions.Length; ++i)
- {
- var zipUrlList = new List<string>();
- SplitUrlList(stableZipUrls[i], zipUrlList);
-
- var fullZipUrlList = new List<string>();
- SplitUrlList(stableFullZipUrls[i], fullZipUrlList);
-
- var netFxVersion = new Version(stableNetFxVersions[i]);
-
- if (netFxVersion.Major == 2 && netFxVersion.Minor == 0)
- {
- netFxVersion = new Version(2, 0, 0); // discard the build # that is specified, since we use that for Service Pack level now
- }
-
- var info = new PdnVersionInfo(
- stableVersions[i],
- stableNames[i],
- netFxVersion.Major,
- netFxVersion.Minor,
- netFxVersion.Build, // service pack
- stableInfoUrls[i],
- zipUrlList.ToArray(),
- fullZipUrlList.ToArray(),
- true);
-
- versionInfos[cursor] = info;
- ++cursor;
- }
-
- for (int i = 0; i < betaVersions.Length; ++i)
- {
- var zipUrlList = new List<string>();
- SplitUrlList(betaZipUrls[i], zipUrlList);
-
- var fullZipUrlList = new List<string>();
- SplitUrlList(betaFullZipUrls[i], fullZipUrlList);
-
- var netFxVersion = new Version(betaNetFxVersions[i]);
-
- if (netFxVersion.Major == 2 && netFxVersion.Minor == 0)
- {
- netFxVersion = new Version(2, 0, 0); // discard the build # that is specified, since we use that for Service Pack level now
- }
-
- var info = new PdnVersionInfo(
- betaVersions[i],
- betaNames[i],
- netFxVersion.Major,
- netFxVersion.Minor,
- netFxVersion.Build, // service pack
- betaInfoUrls[i],
- zipUrlList.ToArray(),
- fullZipUrlList.ToArray(),
- false);
-
- versionInfos[cursor] = info;
- ++cursor;
- }
-
- var manifest = new PdnVersionManifest(downloadPageUrl, versionInfos);
- exception = null;
- return manifest;
- }
-
- catch (Exception ex)
- {
- exception = ex;
- return null;
- }
- }
-
- private static void CheckForUpdates(
- out PdnVersionManifest manifestResult,
- out int latestVersionIndexResult,
- out Exception exception)
- {
- exception = null;
- PdnVersionManifest manifest = null;
- manifestResult = null;
- latestVersionIndexResult = -1;
-
- int retries = 2;
-
- while (retries > 0)
- {
- try
- {
- manifest = GetUpdatesManifest(out exception);
- retries = 0;
- }
-
- catch (Exception ex)
- {
- exception = ex;
- --retries;
-
- if (retries == 0)
- {
- manifest = null;
- }
- }
- }
-
- if (manifest != null)
- {
- int stableIndex = manifest.GetLatestStableVersionIndex();
- int betaIndex = manifest.GetLatestBetaVersionIndex();
-
- // Check for betas as well?
- bool checkForBetas = ("1" == Settings.SystemWide.GetString(SettingNames.AlsoCheckForBetas, "0"));
-
- // Figure out which version we want to compare against the current version
- int latestIndex = stableIndex;
-
- if (checkForBetas)
- {
- // If they like betas, and if the beta is newer than the latest stable release,
- // then offer it to them.
- if (betaIndex != -1 &&
- (stableIndex == -1 || manifest.VersionInfos[betaIndex].Version >= manifest.VersionInfos[stableIndex].Version))
- {
- latestIndex = betaIndex;
- }
- }
-
- // Now compare that version against the current version
- if (latestIndex != -1)
- {
- if (PdnInfo.IsTestMode ||
- manifest.VersionInfos[latestIndex].Version > PdnInfo.GetVersion())
- {
- manifestResult = manifest;
- latestVersionIndexResult = latestIndex;
- }
- }
- }
- }
-
- public override bool CanAbort
- {
- get
- {
- return true;
- }
- }
-
- protected override void OnAbort()
- {
- _abortEvent.Set();
- base.OnAbort();
- }
-
- private void DoCheckThreadProc(object ignored)
- {
- try
- {
- Thread.Sleep(1500);
- CheckForUpdates(out _manifest, out _latestVersionIndex, out _exception);
- }
-
- finally
- {
- _checkingEvent.Set();
- }
- }
-
- public override void OnEnteredState()
- {
- _checkingEvent.Reset();
- _abortEvent.Reset();
-
- ThreadPool.QueueUserWorkItem(DoCheckThreadProc);
-
- var events = new WaitHandleArray(2);
- events[0] = _checkingEvent;
- events[1] = _abortEvent;
- int waitResult = events.WaitAny();
-
- if (waitResult == 0 && _manifest != null && _latestVersionIndex != -1)
- {
- StateMachine.QueueInput(PrivateInput.GoToUpdateAvailable);
- }
- else if (waitResult == 1)
- {
- StateMachine.QueueInput(PrivateInput.GoToAborted);
- }
- else if (_exception != null)
- {
- StateMachine.QueueInput(PrivateInput.GoToError);
- }
- else
- {
- StateMachine.QueueInput(PrivateInput.GoToDone);
- }
- }
-
- public override void ProcessInput(object input, out State newState)
- {
- if (input.Equals(PrivateInput.GoToUpdateAvailable))
- {
- newState = new UpdateAvailableState(_manifest.VersionInfos[_latestVersionIndex]);
- }
- else if (input.Equals(PrivateInput.GoToError))
- {
- string errorMessage;
-
- if (_exception is WebException)
- {
- errorMessage = Utility.WebExceptionToErrorMessage((WebException)_exception);
- }
- else
- {
- errorMessage = PdnResources.GetString("Updates.CheckingState.GenericError");
- }
-
- newState = new ErrorState(_exception, errorMessage);
- }
- else if (input.Equals(PrivateInput.GoToDone))
- {
- newState = new DoneState();
- }
- else if (input.Equals(PrivateInput.GoToAborted))
- {
- newState = new AbortedState();
- }
- else
- {
- throw new ArgumentException();
- }
- }
-
- public CheckingState()
- : base(false, false, MarqueeStyle.Marquee)
- {
- }
- }
- }