From b9c9e05a94d4b434ad57bc446afeaa156c7d343e Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Sat, 17 Jun 2023 10:49:15 +0200 Subject: [PATCH] Disentangle AboutPage from checking for updates (prepare for the later possibility of introducing AutoUpdate for MSI installs) --- ILSpy/AboutPage.cs | 166 +---------------------- ILSpy/MainWindow.xaml.cs | 8 +- ILSpy/Updates/AppUpdateService.cs | 35 +++++ ILSpy/Updates/AvailableVersionInfo.cs | 28 ++++ ILSpy/Updates/NotifyOfUpdatesStrategy.cs | 114 ++++++++++++++++ ILSpy/Updates/UpdateSettings.cs | 89 ++++++++++++ 6 files changed, 277 insertions(+), 163 deletions(-) create mode 100644 ILSpy/Updates/AppUpdateService.cs create mode 100644 ILSpy/Updates/AvailableVersionInfo.cs create mode 100644 ILSpy/Updates/NotifyOfUpdatesStrategy.cs create mode 100644 ILSpy/Updates/UpdateSettings.cs diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index 0ac9866bf2..1dcef578ab 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -17,24 +17,20 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.ComponentModel; using System.IO; -using System.Linq; -using System.Net.Http; using System.Text.RegularExpressions; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Navigation; -using System.Xml.Linq; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.Themes; +using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpyX.Settings; namespace ICSharpCode.ILSpy @@ -50,11 +46,6 @@ public override void Execute(object parameter) ); } - static readonly Uri UpdateUrl = new Uri("https://ilspy.net/updates.xml"); - const string band = "stable"; - - static AvailableVersionInfo latestAvailableVersion; - public static void Display(DecompilerTextView textView) { AvalonEditTextOutput output = new AvalonEditTextOutput() { @@ -71,14 +62,14 @@ public static void Display(DecompilerTextView textView) StackPanel stackPanel = new StackPanel(); stackPanel.HorizontalAlignment = HorizontalAlignment.Center; stackPanel.Orientation = Orientation.Horizontal; - if (latestAvailableVersion == null) + if (NotifyOfUpdatesStrategy.LatestAvailableVersion == null) { AddUpdateCheckButton(stackPanel, textView); } else { // we already retrieved the latest version sometime earlier - ShowAvailableVersion(latestAvailableVersion, stackPanel); + ShowAvailableVersion(NotifyOfUpdatesStrategy.LatestAvailableVersion, stackPanel); } CheckBox checkBox = new CheckBox(); checkBox.Margin = new Thickness(4); @@ -142,7 +133,7 @@ static void AddUpdateCheckButton(StackPanel stackPanel, DecompilerTextView textV try { - AvailableVersionInfo vInfo = await GetLatestVersionAsync(); + AvailableVersionInfo vInfo = await NotifyOfUpdatesStrategy.GetLatestVersionAsync(); stackPanel.Children.Clear(); ShowAvailableVersion(vInfo, stackPanel); } @@ -155,11 +146,9 @@ static void AddUpdateCheckButton(StackPanel stackPanel, DecompilerTextView textV }; } - static readonly Version currentVersion = new Version(DecompilerVersionInfo.Major + "." + DecompilerVersionInfo.Minor + "." + DecompilerVersionInfo.Build + "." + DecompilerVersionInfo.Revision); - static void ShowAvailableVersion(AvailableVersionInfo availableVersion, StackPanel stackPanel) { - if (currentVersion == availableVersion.Version) + if (AppUpdateService.CurrentVersion == availableVersion.Version) { stackPanel.Children.Add( new Image { @@ -173,7 +162,7 @@ static void ShowAvailableVersion(AvailableVersionInfo availableVersion, StackPan VerticalAlignment = VerticalAlignment.Bottom }); } - else if (currentVersion < availableVersion.Version) + else if (AppUpdateService.CurrentVersion < availableVersion.Version) { stackPanel.Children.Add( new TextBlock { @@ -197,149 +186,6 @@ static void ShowAvailableVersion(AvailableVersionInfo availableVersion, StackPan stackPanel.Children.Add(new TextBlock { Text = Resources.UsingNightlyBuildNewerThanLatestRelease }); } } - - static async Task GetLatestVersionAsync() - { - var client = new HttpClient(new HttpClientHandler() { - UseProxy = true, - UseDefaultCredentials = true, - }); - string data = await client.GetStringAsync(UpdateUrl); - - XDocument doc = XDocument.Load(new StringReader(data)); - var bands = doc.Root.Elements("band"); - var currentBand = bands.FirstOrDefault(b => (string)b.Attribute("id") == band) ?? bands.First(); - Version version = new Version((string)currentBand.Element("latestVersion")); - string url = (string)currentBand.Element("downloadUrl"); - if (!(url.StartsWith("http://", StringComparison.Ordinal) || url.StartsWith("https://", StringComparison.Ordinal))) - url = null; // don't accept non-urls - - latestAvailableVersion = new AvailableVersionInfo { Version = version, DownloadUrl = url }; - return latestAvailableVersion; - } - - sealed class AvailableVersionInfo - { - public Version Version; - public string DownloadUrl; - } - - sealed class UpdateSettings : INotifyPropertyChanged - { - public UpdateSettings(ILSpySettings spySettings) - { - XElement s = spySettings["UpdateSettings"]; - this.automaticUpdateCheckEnabled = (bool?)s.Element("AutomaticUpdateCheckEnabled") ?? true; - try - { - this.lastSuccessfulUpdateCheck = (DateTime?)s.Element("LastSuccessfulUpdateCheck"); - } - catch (FormatException) - { - // avoid crashing on settings files invalid due to - // https://github.com/icsharpcode/ILSpy/issues/closed/#issue/2 - } - } - - bool automaticUpdateCheckEnabled; - - public bool AutomaticUpdateCheckEnabled { - get { return automaticUpdateCheckEnabled; } - set { - if (automaticUpdateCheckEnabled != value) - { - automaticUpdateCheckEnabled = value; - Save(); - OnPropertyChanged(nameof(AutomaticUpdateCheckEnabled)); - } - } - } - - DateTime? lastSuccessfulUpdateCheck; - - public DateTime? LastSuccessfulUpdateCheck { - get { return lastSuccessfulUpdateCheck; } - set { - if (lastSuccessfulUpdateCheck != value) - { - lastSuccessfulUpdateCheck = value; - Save(); - OnPropertyChanged(nameof(LastSuccessfulUpdateCheck)); - } - } - } - - public void Save() - { - XElement updateSettings = new XElement("UpdateSettings"); - updateSettings.Add(new XElement("AutomaticUpdateCheckEnabled", automaticUpdateCheckEnabled)); - if (lastSuccessfulUpdateCheck != null) - updateSettings.Add(new XElement("LastSuccessfulUpdateCheck", lastSuccessfulUpdateCheck)); - ILSpySettings.SaveSettings(updateSettings); - } - - public event PropertyChangedEventHandler PropertyChanged; - - void OnPropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - } - - /// - /// If automatic update checking is enabled, checks if there are any updates available. - /// Returns the download URL if an update is available. - /// Returns null if no update is available, or if no check was performed. - /// - public static async Task CheckForUpdatesIfEnabledAsync(ILSpySettings spySettings) - { - UpdateSettings s = new UpdateSettings(spySettings); - - // If we're in an MSIX package, updates work differently - if (s.AutomaticUpdateCheckEnabled) - { - // perform update check if we never did one before; - // or if the last check wasn't in the past 7 days - if (s.LastSuccessfulUpdateCheck == null - || s.LastSuccessfulUpdateCheck < DateTime.UtcNow.AddDays(-7) - || s.LastSuccessfulUpdateCheck > DateTime.UtcNow) - { - return await CheckForUpdateInternal(s); - } - else - { - return null; - } - } - else - { - return null; - } - } - - public static Task CheckForUpdatesAsync(ILSpySettings spySettings) - { - UpdateSettings s = new UpdateSettings(spySettings); - return CheckForUpdateInternal(s); - } - - static async Task CheckForUpdateInternal(UpdateSettings s) - { - try - { - var v = await GetLatestVersionAsync(); - s.LastSuccessfulUpdateCheck = DateTime.UtcNow; - if (v.Version > currentVersion) - return v.DownloadUrl; - else - return null; - } - catch (Exception) - { - // ignore errors getting the version info - return null; - } - } } /// diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index eaa65475ba..c31d35affe 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -50,6 +50,7 @@ using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Settings; @@ -954,13 +955,14 @@ public async Task ShowMessageIfUpdatesAvailableAsync(ILSpySettings spySettings, string downloadUrl; if (forceCheck) { - downloadUrl = await AboutPage.CheckForUpdatesAsync(spySettings); + downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(spySettings); } else { - downloadUrl = await AboutPage.CheckForUpdatesIfEnabledAsync(spySettings); + downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesIfEnabledAsync(spySettings); } + // The Update Panel is only available for NotifyOfUpdatesStrategy, AutoUpdate will have differing UI requirements AdjustUpdateUIAfterCheck(downloadUrl, forceCheck); } @@ -978,7 +980,7 @@ async void downloadOrCheckUpdateButtonClick(object sender, RoutedEventArgs e) else { updatePanel.Visibility = Visibility.Collapsed; - string downloadUrl = await AboutPage.CheckForUpdatesAsync(ILSpySettings.Load()); + string downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(ILSpySettings.Load()); AdjustUpdateUIAfterCheck(downloadUrl, true); } } diff --git a/ILSpy/Updates/AppUpdateService.cs b/ILSpy/Updates/AppUpdateService.cs new file mode 100644 index 0000000000..04b3f1635b --- /dev/null +++ b/ILSpy/Updates/AppUpdateService.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace ICSharpCode.ILSpy.Updates +{ + internal enum UpdateStrategy + { + NotifyOfUpdates, + // AutoUpdate + } + + internal static class AppUpdateService + { + public static readonly UpdateStrategy updateStrategy = UpdateStrategy.NotifyOfUpdates; + + public static readonly Version CurrentVersion = new Version(DecompilerVersionInfo.Major + "." + DecompilerVersionInfo.Minor + "." + DecompilerVersionInfo.Build + "." + DecompilerVersionInfo.Revision); + } +} diff --git a/ILSpy/Updates/AvailableVersionInfo.cs b/ILSpy/Updates/AvailableVersionInfo.cs new file mode 100644 index 0000000000..995619b5d5 --- /dev/null +++ b/ILSpy/Updates/AvailableVersionInfo.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; + +namespace ICSharpCode.ILSpy.Updates +{ + sealed class AvailableVersionInfo + { + public Version Version; + public string DownloadUrl; + } +} diff --git a/ILSpy/Updates/NotifyOfUpdatesStrategy.cs b/ILSpy/Updates/NotifyOfUpdatesStrategy.cs new file mode 100644 index 0000000000..1d5123c261 --- /dev/null +++ b/ILSpy/Updates/NotifyOfUpdatesStrategy.cs @@ -0,0 +1,114 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using System.Xml.Linq; + +using ICSharpCode.ILSpyX.Settings; + +namespace ICSharpCode.ILSpy.Updates +{ + internal static class NotifyOfUpdatesStrategy + { + static readonly Uri UpdateUrl = new Uri("https://ilspy.net/updates.xml"); + const string band = "stable"; + + public static AvailableVersionInfo LatestAvailableVersion { get; private set; } + + public static async Task GetLatestVersionAsync() + { + var client = new HttpClient(new HttpClientHandler() { + UseProxy = true, + UseDefaultCredentials = true, + }); + string data = await client.GetStringAsync(UpdateUrl).ConfigureAwait(false); + + XDocument doc = XDocument.Load(new StringReader(data)); + var bands = doc.Root.Elements("band"); + var currentBand = bands.FirstOrDefault(b => (string)b.Attribute("id") == band) ?? bands.First(); + Version version = new Version((string)currentBand.Element("latestVersion")); + string url = (string)currentBand.Element("downloadUrl"); + if (!(url.StartsWith("http://", StringComparison.Ordinal) || url.StartsWith("https://", StringComparison.Ordinal))) + url = null; // don't accept non-urls + + LatestAvailableVersion = new AvailableVersionInfo { Version = version, DownloadUrl = url }; + return LatestAvailableVersion; + } + + + + /// + /// If automatic update checking is enabled, checks if there are any updates available. + /// Returns the download URL if an update is available. + /// Returns null if no update is available, or if no check was performed. + /// + public static async Task CheckForUpdatesIfEnabledAsync(ILSpySettings spySettings) + { + UpdateSettings s = new UpdateSettings(spySettings); + + // If we're in an MSIX package, updates work differently + if (s.AutomaticUpdateCheckEnabled) + { + // perform update check if we never did one before; + // or if the last check wasn't in the past 7 days + if (s.LastSuccessfulUpdateCheck == null + || s.LastSuccessfulUpdateCheck < DateTime.UtcNow.AddDays(-7) + || s.LastSuccessfulUpdateCheck > DateTime.UtcNow) + { + return await CheckForUpdateInternal(s).ConfigureAwait(false); + } + else + { + return null; + } + } + else + { + return null; + } + } + + public static Task CheckForUpdatesAsync(ILSpySettings spySettings) + { + UpdateSettings s = new UpdateSettings(spySettings); + return CheckForUpdateInternal(s); + } + + static async Task CheckForUpdateInternal(UpdateSettings s) + { + try + { + var v = await GetLatestVersionAsync().ConfigureAwait(false); + s.LastSuccessfulUpdateCheck = DateTime.UtcNow; + if (v.Version > AppUpdateService.CurrentVersion) + return v.DownloadUrl; + else + return null; + } + catch (Exception) + { + // ignore errors getting the version info + return null; + } + } + } +} diff --git a/ILSpy/Updates/UpdateSettings.cs b/ILSpy/Updates/UpdateSettings.cs new file mode 100644 index 0000000000..777f23eee1 --- /dev/null +++ b/ILSpy/Updates/UpdateSettings.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.ComponentModel; +using System.Xml.Linq; + +using ICSharpCode.ILSpyX.Settings; + +namespace ICSharpCode.ILSpy.Updates +{ + sealed class UpdateSettings : INotifyPropertyChanged + { + public UpdateSettings(ILSpySettings spySettings) + { + XElement s = spySettings["UpdateSettings"]; + this.automaticUpdateCheckEnabled = (bool?)s.Element("AutomaticUpdateCheckEnabled") ?? true; + try + { + this.lastSuccessfulUpdateCheck = (DateTime?)s.Element("LastSuccessfulUpdateCheck"); + } + catch (FormatException) + { + // avoid crashing on settings files invalid due to + // https://github.com/icsharpcode/ILSpy/issues/closed/#issue/2 + } + } + + bool automaticUpdateCheckEnabled; + + public bool AutomaticUpdateCheckEnabled { + get { return automaticUpdateCheckEnabled; } + set { + if (automaticUpdateCheckEnabled != value) + { + automaticUpdateCheckEnabled = value; + Save(); + OnPropertyChanged(nameof(AutomaticUpdateCheckEnabled)); + } + } + } + + DateTime? lastSuccessfulUpdateCheck; + + public DateTime? LastSuccessfulUpdateCheck { + get { return lastSuccessfulUpdateCheck; } + set { + if (lastSuccessfulUpdateCheck != value) + { + lastSuccessfulUpdateCheck = value; + Save(); + OnPropertyChanged(nameof(LastSuccessfulUpdateCheck)); + } + } + } + + public void Save() + { + XElement updateSettings = new XElement("UpdateSettings"); + updateSettings.Add(new XElement("AutomaticUpdateCheckEnabled", automaticUpdateCheckEnabled)); + if (lastSuccessfulUpdateCheck != null) + updateSettings.Add(new XElement("LastSuccessfulUpdateCheck", lastSuccessfulUpdateCheck)); + ILSpySettings.SaveSettings(updateSettings); + } + + public event PropertyChangedEventHandler PropertyChanged; + + void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + +}