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.

336 lines
12 KiB

  1. // Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.ComponentModel;
  20. using System.ComponentModel.Composition;
  21. using System.Diagnostics;
  22. using System.IO;
  23. using System.Linq;
  24. using System.Net;
  25. using System.Text.RegularExpressions;
  26. using System.Threading.Tasks;
  27. using System.Windows;
  28. using System.Windows.Controls;
  29. using System.Windows.Data;
  30. using System.Windows.Input;
  31. using System.Xml.Linq;
  32. using ICSharpCode.AvalonEdit.Rendering;
  33. using ICSharpCode.Decompiler;
  34. using ICSharpCode.ILSpy.TextView;
  35. namespace ICSharpCode.ILSpy
  36. {
  37. [ExportMainMenuCommand(Menu = "_Help", Header = "_About", MenuOrder = 99999)]
  38. sealed class AboutPage : SimpleCommand
  39. {
  40. [Import]
  41. DecompilerTextView decompilerTextView = null;
  42. public override void Execute(object parameter)
  43. {
  44. MainWindow.Instance.UnselectAll();
  45. Display(decompilerTextView);
  46. }
  47. static readonly Uri UpdateUrl = new Uri("https://ilspy.net/updates.xml");
  48. const string band = "stable";
  49. static AvailableVersionInfo latestAvailableVersion;
  50. public static void Display(DecompilerTextView textView)
  51. {
  52. AvalonEditTextOutput output = new AvalonEditTextOutput();
  53. output.WriteLine("ILSpy version " + RevisionClass.FullVersion);
  54. output.AddUIElement(
  55. delegate {
  56. StackPanel stackPanel = new StackPanel();
  57. stackPanel.HorizontalAlignment = HorizontalAlignment.Center;
  58. stackPanel.Orientation = Orientation.Horizontal;
  59. if (latestAvailableVersion == null) {
  60. AddUpdateCheckButton(stackPanel, textView);
  61. } else {
  62. // we already retrieved the latest version sometime earlier
  63. ShowAvailableVersion(latestAvailableVersion, stackPanel);
  64. }
  65. CheckBox checkBox = new CheckBox();
  66. checkBox.Margin = new Thickness(4);
  67. checkBox.Content = "Automatically check for updates every week";
  68. UpdateSettings settings = new UpdateSettings(ILSpySettings.Load());
  69. checkBox.SetBinding(CheckBox.IsCheckedProperty, new Binding("AutomaticUpdateCheckEnabled") { Source = settings });
  70. return new StackPanel {
  71. Margin = new Thickness(0, 4, 0, 0),
  72. Cursor = Cursors.Arrow,
  73. Children = { stackPanel, checkBox }
  74. };
  75. });
  76. output.WriteLine();
  77. foreach (var plugin in App.ExportProvider.GetExportedValues<IAboutPageAddition>())
  78. plugin.Write(output);
  79. output.WriteLine();
  80. using (Stream s = typeof(AboutPage).Assembly.GetManifestResourceStream(typeof(AboutPage), "README.txt")) {
  81. using (StreamReader r = new StreamReader(s)) {
  82. string line;
  83. while ((line = r.ReadLine()) != null) {
  84. output.WriteLine(line);
  85. }
  86. }
  87. }
  88. output.AddVisualLineElementGenerator(new MyLinkElementGenerator("SharpDevelop", "http://www.icsharpcode.net/opensource/sd/"));
  89. output.AddVisualLineElementGenerator(new MyLinkElementGenerator("MIT License", "resource:license.txt"));
  90. output.AddVisualLineElementGenerator(new MyLinkElementGenerator("LGPL", "resource:LGPL.txt"));
  91. output.AddVisualLineElementGenerator(new MyLinkElementGenerator("MS-PL", "resource:MS-PL.txt"));
  92. textView.ShowText(output);
  93. }
  94. sealed class MyLinkElementGenerator : LinkElementGenerator
  95. {
  96. readonly Uri uri;
  97. public MyLinkElementGenerator(string matchText, string url) : base(new Regex(Regex.Escape(matchText)))
  98. {
  99. this.uri = new Uri(url);
  100. this.RequireControlModifierForClick = false;
  101. }
  102. protected override Uri GetUriFromMatch(Match match)
  103. {
  104. return uri;
  105. }
  106. }
  107. static void AddUpdateCheckButton(StackPanel stackPanel, DecompilerTextView textView)
  108. {
  109. Button button = new Button();
  110. button.Content = "Check for updates";
  111. button.Cursor = Cursors.Arrow;
  112. stackPanel.Children.Add(button);
  113. button.Click += delegate {
  114. button.Content = "Checking...";
  115. button.IsEnabled = false;
  116. GetLatestVersionAsync().ContinueWith(
  117. delegate (Task<AvailableVersionInfo> task) {
  118. try {
  119. stackPanel.Children.Clear();
  120. ShowAvailableVersion(task.Result, stackPanel);
  121. } catch (Exception ex) {
  122. AvalonEditTextOutput exceptionOutput = new AvalonEditTextOutput();
  123. exceptionOutput.WriteLine(ex.ToString());
  124. textView.ShowText(exceptionOutput);
  125. }
  126. }, TaskScheduler.FromCurrentSynchronizationContext());
  127. };
  128. }
  129. static readonly Version currentVersion = new Version(RevisionClass.Major + "." + RevisionClass.Minor + "." + RevisionClass.Build + "." + RevisionClass.Revision);
  130. static void ShowAvailableVersion(AvailableVersionInfo availableVersion, StackPanel stackPanel)
  131. {
  132. if (currentVersion == availableVersion.Version) {
  133. stackPanel.Children.Add(
  134. new Image {
  135. Width = 16, Height = 16,
  136. Source = Images.OK,
  137. Margin = new Thickness(4,0,4,0)
  138. });
  139. stackPanel.Children.Add(
  140. new TextBlock {
  141. Text = "You are using the latest release.",
  142. VerticalAlignment = VerticalAlignment.Bottom
  143. });
  144. } else if (currentVersion < availableVersion.Version) {
  145. stackPanel.Children.Add(
  146. new TextBlock {
  147. Text = "Version " + availableVersion.Version + " is available.",
  148. Margin = new Thickness(0,0,8,0),
  149. VerticalAlignment = VerticalAlignment.Bottom
  150. });
  151. if (availableVersion.DownloadUrl != null) {
  152. Button button = new Button();
  153. button.Content = "Download";
  154. button.Cursor = Cursors.Arrow;
  155. button.Click += delegate {
  156. MainWindow.OpenLink(availableVersion.DownloadUrl);
  157. };
  158. stackPanel.Children.Add(button);
  159. }
  160. } else {
  161. stackPanel.Children.Add(new TextBlock { Text = "You are using a nightly build newer than the latest release." });
  162. }
  163. }
  164. static Task<AvailableVersionInfo> GetLatestVersionAsync()
  165. {
  166. var tcs = new TaskCompletionSource<AvailableVersionInfo>();
  167. new Action(() => {
  168. WebClient wc = new WebClient();
  169. IWebProxy systemWebProxy = WebRequest.GetSystemWebProxy();
  170. systemWebProxy.Credentials = CredentialCache.DefaultCredentials;
  171. wc.Proxy = systemWebProxy;
  172. wc.DownloadDataCompleted += delegate(object sender, DownloadDataCompletedEventArgs e) {
  173. if (e.Error != null) {
  174. tcs.SetException(e.Error);
  175. } else {
  176. try {
  177. XDocument doc = XDocument.Load(new MemoryStream(e.Result));
  178. var bands = doc.Root.Elements("band");
  179. var currentBand = bands.FirstOrDefault(b => (string)b.Attribute("id") == band) ?? bands.First();
  180. Version version = new Version((string)currentBand.Element("latestVersion"));
  181. string url = (string)currentBand.Element("downloadUrl");
  182. if (!(url.StartsWith("http://", StringComparison.Ordinal) || url.StartsWith("https://", StringComparison.Ordinal)))
  183. url = null; // don't accept non-urls
  184. latestAvailableVersion = new AvailableVersionInfo { Version = version, DownloadUrl = url };
  185. tcs.SetResult(latestAvailableVersion);
  186. } catch (Exception ex) {
  187. tcs.SetException(ex);
  188. }
  189. }
  190. };
  191. wc.DownloadDataAsync(UpdateUrl);
  192. }).BeginInvoke(null, null);
  193. return tcs.Task;
  194. }
  195. sealed class AvailableVersionInfo
  196. {
  197. public Version Version;
  198. public string DownloadUrl;
  199. }
  200. sealed class UpdateSettings : INotifyPropertyChanged
  201. {
  202. public UpdateSettings(ILSpySettings spySettings)
  203. {
  204. XElement s = spySettings["UpdateSettings"];
  205. this.automaticUpdateCheckEnabled = (bool?)s.Element("AutomaticUpdateCheckEnabled") ?? true;
  206. try {
  207. this.lastSuccessfulUpdateCheck = (DateTime?)s.Element("LastSuccessfulUpdateCheck");
  208. } catch (FormatException) {
  209. // avoid crashing on settings files invalid due to
  210. // https://github.com/icsharpcode/ILSpy/issues/closed/#issue/2
  211. }
  212. }
  213. bool automaticUpdateCheckEnabled;
  214. public bool AutomaticUpdateCheckEnabled {
  215. get { return automaticUpdateCheckEnabled; }
  216. set {
  217. if (automaticUpdateCheckEnabled != value) {
  218. automaticUpdateCheckEnabled = value;
  219. Save();
  220. OnPropertyChanged(nameof(AutomaticUpdateCheckEnabled));
  221. }
  222. }
  223. }
  224. DateTime? lastSuccessfulUpdateCheck;
  225. public DateTime? LastSuccessfulUpdateCheck {
  226. get { return lastSuccessfulUpdateCheck; }
  227. set {
  228. if (lastSuccessfulUpdateCheck != value) {
  229. lastSuccessfulUpdateCheck = value;
  230. Save();
  231. OnPropertyChanged(nameof(LastSuccessfulUpdateCheck));
  232. }
  233. }
  234. }
  235. public void Save()
  236. {
  237. XElement updateSettings = new XElement("UpdateSettings");
  238. updateSettings.Add(new XElement("AutomaticUpdateCheckEnabled", automaticUpdateCheckEnabled));
  239. if (lastSuccessfulUpdateCheck != null)
  240. updateSettings.Add(new XElement("LastSuccessfulUpdateCheck", lastSuccessfulUpdateCheck));
  241. ILSpySettings.SaveSettings(updateSettings);
  242. }
  243. public event PropertyChangedEventHandler PropertyChanged;
  244. void OnPropertyChanged(string propertyName)
  245. {
  246. if (PropertyChanged != null) {
  247. PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  248. }
  249. }
  250. }
  251. /// <summary>
  252. /// If automatic update checking is enabled, checks if there are any updates available.
  253. /// Returns the download URL if an update is available.
  254. /// Returns null if no update is available, or if no check was performed.
  255. /// </summary>
  256. public static Task<string> CheckForUpdatesIfEnabledAsync(ILSpySettings spySettings)
  257. {
  258. var tcs = new TaskCompletionSource<string>();
  259. UpdateSettings s = new UpdateSettings(spySettings);
  260. if (s.AutomaticUpdateCheckEnabled) {
  261. // perform update check if we never did one before;
  262. // or if the last check wasn't in the past 7 days
  263. if (s.LastSuccessfulUpdateCheck == null
  264. || s.LastSuccessfulUpdateCheck < DateTime.UtcNow.AddDays(-7)
  265. || s.LastSuccessfulUpdateCheck > DateTime.UtcNow)
  266. {
  267. CheckForUpdateInternal(tcs, s);
  268. } else {
  269. tcs.SetResult(null);
  270. }
  271. } else {
  272. tcs.SetResult(null);
  273. }
  274. return tcs.Task;
  275. }
  276. public static Task<string> CheckForUpdatesAsync(ILSpySettings spySettings)
  277. {
  278. var tcs = new TaskCompletionSource<string>();
  279. UpdateSettings s = new UpdateSettings(spySettings);
  280. CheckForUpdateInternal(tcs, s);
  281. return tcs.Task;
  282. }
  283. static void CheckForUpdateInternal(TaskCompletionSource<string> tcs, UpdateSettings s)
  284. {
  285. GetLatestVersionAsync().ContinueWith(
  286. delegate (Task<AvailableVersionInfo> task) {
  287. try {
  288. s.LastSuccessfulUpdateCheck = DateTime.UtcNow;
  289. AvailableVersionInfo v = task.Result;
  290. if (v.Version > currentVersion)
  291. tcs.SetResult(v.DownloadUrl);
  292. else
  293. tcs.SetResult(null);
  294. } catch (AggregateException) {
  295. // ignore errors getting the version info
  296. tcs.SetResult(null);
  297. }
  298. });
  299. }
  300. }
  301. /// <summary>
  302. /// Interface that allows plugins to extend the about page.
  303. /// </summary>
  304. public interface IAboutPageAddition
  305. {
  306. void Write(ISmartTextOutput textOutput);
  307. }
  308. }