From 4eeea762a2d745bae16520d6c76ca56a7b783395 Mon Sep 17 00:00:00 2001 From: ElektroKill Date: Sun, 17 Sep 2023 21:53:43 +0200 Subject: [PATCH] Add update checker which runs on startup --- dnSpy/dnSpy/MainApp/AboutScreen.cs | 22 +- .../Settings/GeneralAppSettingsPage.cs | 24 ++- dnSpy/dnSpy/MainApp/StartupUpdateCheck.cs | 75 +++++++ dnSpy/dnSpy/MainApp/UpdateChecker.cs | 100 --------- dnSpy/dnSpy/MainApp/UpdateService.cs | 172 ++++++++++++++++ .../Properties/dnSpy.Resources.Designer.cs | 45 ++++ dnSpy/dnSpy/Properties/dnSpy.Resources.resx | 15 ++ dnSpy/dnSpy/Themes/wpf.styles.templates.xaml | 194 +++++++++--------- 8 files changed, 441 insertions(+), 206 deletions(-) create mode 100644 dnSpy/dnSpy/MainApp/StartupUpdateCheck.cs delete mode 100644 dnSpy/dnSpy/MainApp/UpdateChecker.cs create mode 100644 dnSpy/dnSpy/MainApp/UpdateService.cs diff --git a/dnSpy/dnSpy/MainApp/AboutScreen.cs b/dnSpy/dnSpy/MainApp/AboutScreen.cs index 6bf224427e..0333078e2f 100644 --- a/dnSpy/dnSpy/MainApp/AboutScreen.cs +++ b/dnSpy/dnSpy/MainApp/AboutScreen.cs @@ -46,13 +46,15 @@ sealed class AboutScreenDocumentTabContentFactory : IDocumentTabContentFactory { readonly IDocumentViewerContentFactoryProvider documentViewerContentFactoryProvider; readonly IAppWindow appWindow; readonly IExtensionService extensionService; + readonly IUpdateService updateService; readonly IContentType aboutContentType; [ImportingConstructor] - AboutScreenDocumentTabContentFactory(IDocumentViewerContentFactoryProvider documentViewerContentFactoryProvider, IAppWindow appWindow, IExtensionService extensionService, IContentTypeRegistryService contentTypeRegistryService) { + AboutScreenDocumentTabContentFactory(IDocumentViewerContentFactoryProvider documentViewerContentFactoryProvider, IAppWindow appWindow, IExtensionService extensionService, IUpdateService updateService, IContentTypeRegistryService contentTypeRegistryService) { this.documentViewerContentFactoryProvider = documentViewerContentFactoryProvider; this.appWindow = appWindow; this.extensionService = extensionService; + this.updateService = updateService; aboutContentType = contentTypeRegistryService.GetContentType(ContentTypes.AboutDnSpy); } @@ -62,7 +64,7 @@ sealed class AboutScreenDocumentTabContentFactory : IDocumentTabContentFactory { public DocumentTabContent? Deserialize(Guid guid, ISettingsSection section, IDocumentTabContentFactoryContext context) { if (guid == GUID_SerializedContent) - return new AboutScreenDocumentTabContent(documentViewerContentFactoryProvider, appWindow, extensionService, aboutContentType); + return new AboutScreenDocumentTabContent(documentViewerContentFactoryProvider, appWindow, extensionService, updateService, aboutContentType); return null; } @@ -79,20 +81,22 @@ sealed class AboutScreenMenuItem : MenuItemBase { readonly IDocumentTabService documentTabService; readonly IAppWindow appWindow; readonly IExtensionService extensionService; + readonly IUpdateService updateService; readonly IContentType aboutContentType; [ImportingConstructor] - AboutScreenMenuItem(IDocumentViewerContentFactoryProvider documentViewerContentFactoryProvider, IDocumentTabService documentTabService, IAppWindow appWindow, IExtensionService extensionService, IContentTypeRegistryService contentTypeRegistryService) { + AboutScreenMenuItem(IDocumentViewerContentFactoryProvider documentViewerContentFactoryProvider, IDocumentTabService documentTabService, IAppWindow appWindow, IExtensionService extensionService, IUpdateService updateService, IContentTypeRegistryService contentTypeRegistryService) { this.documentViewerContentFactoryProvider = documentViewerContentFactoryProvider; this.documentTabService = documentTabService; this.appWindow = appWindow; this.extensionService = extensionService; + this.updateService = updateService; aboutContentType = contentTypeRegistryService.GetContentType(ContentTypes.AboutDnSpy); } public override void Execute(IMenuItemContext context) { var tab = documentTabService.GetOrCreateActiveTab(); - tab.Show(new AboutScreenDocumentTabContent(documentViewerContentFactoryProvider, appWindow, extensionService, aboutContentType), null, null); + tab.Show(new AboutScreenDocumentTabContent(documentViewerContentFactoryProvider, appWindow, extensionService, updateService, aboutContentType), null, null); documentTabService.SetFocus(tab); } } @@ -102,17 +106,19 @@ sealed class AboutScreenDocumentTabContent : DocumentTabContent { readonly IAppWindow appWindow; readonly IExtensionService extensionService; + readonly IUpdateService updateService; readonly IContentType aboutContentType; readonly IDocumentViewerContentFactoryProvider documentViewerContentFactoryProvider; - public AboutScreenDocumentTabContent(IDocumentViewerContentFactoryProvider documentViewerContentFactoryProvider, IAppWindow appWindow, IExtensionService extensionService, IContentType aboutContentType) { + public AboutScreenDocumentTabContent(IDocumentViewerContentFactoryProvider documentViewerContentFactoryProvider, IAppWindow appWindow, IExtensionService extensionService, IUpdateService updateService, IContentType aboutContentType) { this.documentViewerContentFactoryProvider = documentViewerContentFactoryProvider; this.appWindow = appWindow; this.extensionService = extensionService; + this.updateService = updateService; this.aboutContentType = aboutContentType; } - public override DocumentTabContent Clone() => new AboutScreenDocumentTabContent(documentViewerContentFactoryProvider, appWindow, extensionService, aboutContentType); + public override DocumentTabContent Clone() => new AboutScreenDocumentTabContent(documentViewerContentFactoryProvider, appWindow, extensionService, updateService, aboutContentType); public override DocumentTabUIContext CreateUIContext(IDocumentTabUIContextLocator locator) => (DocumentTabUIContext)locator.Get(); public override void OnShow(IShowContext ctx) { @@ -224,14 +230,14 @@ void Write(IDocumentViewerOutput output) { WriteResourceFile(output, "dnSpy.LicenseInfo.CREDITS.txt"); } - static async void OnUpdateButtonClick(object sender, RoutedEventArgs e) { + async void OnUpdateButtonClick(object sender, RoutedEventArgs e) { e.Handled = true; var button = (Button)sender; button.IsEnabled = false; button.Content = dnSpy_Resources.AboutScreen_CheckingForUpdates; - var updateResult = await UpdateChecker.CheckForUpdatesAsync(); + var updateResult = await updateService.CheckForUpdatesAsync(); if (!updateResult.Success) MsgBox.Instance.Show(dnSpy_Resources.AboutScreen_FailedToRetrieveUpdateInfo); else if (!updateResult.UpdateAvailable) diff --git a/dnSpy/dnSpy/MainApp/Settings/GeneralAppSettingsPage.cs b/dnSpy/dnSpy/MainApp/Settings/GeneralAppSettingsPage.cs index 2f898b3ee6..77d3d80faa 100644 --- a/dnSpy/dnSpy/MainApp/Settings/GeneralAppSettingsPage.cs +++ b/dnSpy/dnSpy/MainApp/Settings/GeneralAppSettingsPage.cs @@ -43,9 +43,10 @@ sealed class GeneralAppSettingsPageProvider : IAppSettingsPageProvider { readonly IDsDocumentServiceSettings documentServiceSettings; readonly AppSettingsImpl appSettings; readonly MessageBoxService messageBoxService; + readonly IUpdateService updateService; [ImportingConstructor] - GeneralAppSettingsPageProvider(IThemeServiceImpl themeService, IWindowsExplorerIntegrationService windowsExplorerIntegrationService, IDocumentTabServiceSettings documentTabServiceSettings, DocumentTreeViewSettingsImpl documentTreeViewSettings, IDsDocumentServiceSettings documentServiceSettings, AppSettingsImpl appSettings, MessageBoxService messageBoxService) { + GeneralAppSettingsPageProvider(IThemeServiceImpl themeService, IWindowsExplorerIntegrationService windowsExplorerIntegrationService, IDocumentTabServiceSettings documentTabServiceSettings, DocumentTreeViewSettingsImpl documentTreeViewSettings, IDsDocumentServiceSettings documentServiceSettings, AppSettingsImpl appSettings, MessageBoxService messageBoxService, IUpdateService updateService) { this.themeService = themeService; this.windowsExplorerIntegrationService = windowsExplorerIntegrationService; this.documentTabServiceSettings = documentTabServiceSettings; @@ -53,10 +54,11 @@ sealed class GeneralAppSettingsPageProvider : IAppSettingsPageProvider { this.documentServiceSettings = documentServiceSettings; this.appSettings = appSettings; this.messageBoxService = messageBoxService; + this.updateService = updateService; } public IEnumerable Create() { - yield return new GeneralAppSettingsPage(themeService, windowsExplorerIntegrationService, documentTabServiceSettings, documentTreeViewSettings, documentServiceSettings, appSettings, messageBoxService); + yield return new GeneralAppSettingsPage(themeService, windowsExplorerIntegrationService, documentTabServiceSettings, documentTreeViewSettings, documentServiceSettings, appSettings, messageBoxService, updateService); } } @@ -74,8 +76,10 @@ sealed class GeneralAppSettingsPage : AppSettingsPage, IAppSettingsPage2 { readonly IDsDocumentServiceSettings documentServiceSettings; readonly AppSettingsImpl appSettings; readonly MessageBoxService messageBoxService; + readonly IUpdateService updateService; public ICommand ClearAllWarningsCommand => new RelayCommand(a => messageBoxService.EnableAllWarnings(), a => messageBoxService.CanEnableAllWarnings); + public ICommand ResetIgnoredUpdatesCommand => new RelayCommand(a => updateService.ResetIgnoredUpdates(), a => updateService.CanResetIgnoredUpdates); public ObservableCollection ThemesVM { get; } @@ -156,7 +160,18 @@ public bool UseMemoryMappedIO { } bool useMemoryMappedIO; - public GeneralAppSettingsPage(IThemeServiceImpl themeService, IWindowsExplorerIntegrationService windowsExplorerIntegrationService, IDocumentTabServiceSettings documentTabServiceSettings, DocumentTreeViewSettingsImpl documentTreeViewSettings, IDsDocumentServiceSettings documentServiceSettings, AppSettingsImpl appSettings, MessageBoxService messageBoxService) { + public bool CheckForUpdateOnStartup { + get => checkForUpdateOnStartup; + set { + if (checkForUpdateOnStartup != value) { + checkForUpdateOnStartup = value; + OnPropertyChanged(nameof(CheckForUpdateOnStartup)); + } + } + } + bool checkForUpdateOnStartup; + + public GeneralAppSettingsPage(IThemeServiceImpl themeService, IWindowsExplorerIntegrationService windowsExplorerIntegrationService, IDocumentTabServiceSettings documentTabServiceSettings, DocumentTreeViewSettingsImpl documentTreeViewSettings, IDsDocumentServiceSettings documentServiceSettings, AppSettingsImpl appSettings, MessageBoxService messageBoxService, IUpdateService updateService) { this.themeService = themeService ?? throw new ArgumentNullException(nameof(themeService)); this.windowsExplorerIntegrationService = windowsExplorerIntegrationService ?? throw new ArgumentNullException(nameof(windowsExplorerIntegrationService)); this.documentTabServiceSettings = documentTabServiceSettings ?? throw new ArgumentNullException(nameof(documentTabServiceSettings)); @@ -164,6 +179,7 @@ public GeneralAppSettingsPage(IThemeServiceImpl themeService, IWindowsExplorerIn this.documentServiceSettings = documentServiceSettings ?? throw new ArgumentNullException(nameof(documentServiceSettings)); this.appSettings = appSettings ?? throw new ArgumentNullException(nameof(appSettings)); this.messageBoxService = messageBoxService ?? throw new ArgumentNullException(nameof(messageBoxService)); + this.updateService = updateService ?? throw new ArgumentNullException(nameof(updateService)); ThemesVM = new ObservableCollection(themeService.VisibleThemes.Select(a => new ThemeVM(a))); if (!ThemesVM.Any(a => a.Theme == themeService.Theme)) @@ -177,6 +193,7 @@ public GeneralAppSettingsPage(IThemeServiceImpl themeService, IWindowsExplorerIn RestoreTabs = documentTabServiceSettings.RestoreTabs; DeserializeResources = documentTreeViewSettings.DeserializeResources; UseMemoryMappedIO = documentServiceSettings.UseMemoryMappedIO; + CheckForUpdateOnStartup = updateService.CheckForUpdatesOnStartup; } public override string[]? GetSearchStrings() => ThemesVM.Select(a => a.Name).ToArray(); @@ -197,6 +214,7 @@ public void OnApply(IAppRefreshSettings appRefreshSettings) { appRefreshSettings.Add(AppSettingsConstants.DISABLE_MEMORY_MAPPED_IO); } + updateService.CheckForUpdatesOnStartup = CheckForUpdateOnStartup; } } diff --git a/dnSpy/dnSpy/MainApp/StartupUpdateCheck.cs b/dnSpy/dnSpy/MainApp/StartupUpdateCheck.cs new file mode 100644 index 0000000000..8163c3e37f --- /dev/null +++ b/dnSpy/dnSpy/MainApp/StartupUpdateCheck.cs @@ -0,0 +1,75 @@ +/* + Copyright (C) 2023 ElektroKill + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using dnSpy.Contracts.App; +using dnSpy.Contracts.Extension; +using dnSpy.Properties; +using dnSpy.UI; + +namespace dnSpy.MainApp { + [ExportAutoLoaded] + sealed class StartupUpdateCheck : IAutoLoaded { + readonly IUpdateService updateService; + readonly IMessageBoxService messageBoxService; + readonly UIDispatcher uiDispatcher; + readonly IAppWindow appWindow; + + [ImportingConstructor] + public StartupUpdateCheck(IUpdateService updateService, IMessageBoxService messageBoxService, IAppWindow appWindow, UIDispatcher uiDispatcher) { + this.updateService = updateService; + this.messageBoxService = messageBoxService; + this.uiDispatcher = uiDispatcher; + this.appWindow = appWindow; + + if (updateService.CheckForUpdatesOnStartup) + Task.Run(CheckForUpdate); + } + + async void CheckForUpdate() { + var updateInfo = await updateService.CheckForUpdatesAsync(); + + if (!updateInfo.Success) + return; + if (!updateInfo.UpdateAvailable || updateService.IsUpdateIgnored(updateInfo.VersionInfo)) + return; + + string message = string.Format(dnSpy_Resources.InfoBar_NewUpdateAvailable, updateInfo.VersionInfo.Version); + DisplayNotification(message, InfoBarIcon.Information, new[] { + new InfoBarInteraction(dnSpy_Resources.InfoBar_OpenDownloadPage, ctx => { + AboutHelpers.OpenWebPage(updateInfo.VersionInfo.DownloadUrl, messageBoxService); + ctx.CloseElement(); + }), + new InfoBarInteraction(dnSpy_Resources.InfoBar_IgnoreThisUpdate, ctx => { + updateService.MarkUpdateAsIgnored(updateInfo.VersionInfo); + ctx.CloseElement(); + }) + }); + } + + void DisplayNotification(string message, InfoBarIcon icon, InfoBarInteraction[] interactions) { + if (uiDispatcher.CheckAccess()) { + appWindow.InfoBar.Show(message, icon, interactions); + return; + } + uiDispatcher.UIBackground(() => appWindow.InfoBar.Show(message, icon, interactions)); + } + } +} diff --git a/dnSpy/dnSpy/MainApp/UpdateChecker.cs b/dnSpy/dnSpy/MainApp/UpdateChecker.cs deleted file mode 100644 index 816da559bb..0000000000 --- a/dnSpy/dnSpy/MainApp/UpdateChecker.cs +++ /dev/null @@ -1,100 +0,0 @@ -/* - Copyright (C) 2022 ElektroKill - - This file is part of dnSpy - - dnSpy is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - dnSpy is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with dnSpy. If not, see . -*/ - -using System; -using System.Diagnostics; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; - -namespace dnSpy.MainApp { - static class UpdateChecker { - internal readonly struct UpdateCheckInfo { - public readonly bool Success; - public readonly bool UpdateAvailable; - public readonly VersionInfo VersionInfo; - - public UpdateCheckInfo(bool updateAvailable, VersionInfo versionInfo) { - Success = true; - UpdateAvailable = updateAvailable; - VersionInfo = versionInfo; - } - } - - internal readonly struct VersionInfo { - public readonly Version Version; - public readonly string DownloadUrl; - - public VersionInfo(Version version, string url) { - Version = version; - DownloadUrl = url; - } - } - - static readonly Version currentVersion = GetCurrentVersion(); - - static Version GetCurrentVersion() { - var currentAsm = typeof(StartUpClass).Assembly; - try { - var fileVer = FileVersionInfo.GetVersionInfo(currentAsm.Location).FileVersion; - if (!string2.IsNullOrEmpty(fileVer)) - return new Version(fileVer); - } - catch { - } - return currentAsm.GetName().Version!; - } - - public static async Task CheckForUpdatesAsync() { - var updateInfo = await TryGetLatestVersionAsync(); - if (updateInfo is not null) { - if (updateInfo.Value.Version > currentVersion) - return new UpdateCheckInfo(true, updateInfo.Value); - return new UpdateCheckInfo(false, default); - } - - return default; - } - - static async Task TryGetLatestVersionAsync() { - try { - string result; - using (var client = new HttpClient(new HttpClientHandler { UseProxy = true, UseDefaultCredentials = true })) { - client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("dnSpyEx", currentVersion.ToString())); - - result = await client.GetStringAsync("https://api.github.com/repos/dnSpyEx/dnSpy/releases/latest"); - } - - var json = JObject.Parse(result); - if (!json.TryGetValue("tag_name", out var tagToken) || tagToken is not JValue tagValue || tagValue.Value is not string tagName) - return null; - if (!json.TryGetValue("html_url", out var urlToken) || urlToken is not JValue urlValue || urlValue.Value is not string htmlUrl) - return null; - if (tagName[0] == 'v') - tagName = tagName.Remove(0, 1); - if (Version.TryParse(tagName, out var version)) - return new VersionInfo(version, htmlUrl); - } - catch { - } - return null; - } - } -} diff --git a/dnSpy/dnSpy/MainApp/UpdateService.cs b/dnSpy/dnSpy/MainApp/UpdateService.cs new file mode 100644 index 0000000000..6215293e27 --- /dev/null +++ b/dnSpy/dnSpy/MainApp/UpdateService.cs @@ -0,0 +1,172 @@ +/* + Copyright (C) 2023 ElektroKill + + This file is part of dnSpy + + dnSpy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dnSpy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dnSpy. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using dnSpy.Contracts.Settings; +using Newtonsoft.Json.Linq; + +namespace dnSpy.MainApp { + public readonly struct UpdateCheckInfo { + public readonly bool Success; + public readonly bool UpdateAvailable; + public readonly VersionInfo VersionInfo; + + public UpdateCheckInfo(bool updateAvailable, VersionInfo versionInfo) { + Success = true; + UpdateAvailable = updateAvailable; + VersionInfo = versionInfo; + } + } + + public readonly struct VersionInfo { + public readonly Version Version; + public readonly string DownloadUrl; + + public VersionInfo(Version version, string url) { + Version = version; + DownloadUrl = url; + } + + public static implicit operator Version(VersionInfo versionInfo) => versionInfo.Version; + } + + public interface IUpdateService { + bool CheckForUpdatesOnStartup { get; set; } + Task CheckForUpdatesAsync(); + void MarkUpdateAsIgnored(Version version); + bool IsUpdateIgnored(Version version); + bool CanResetIgnoredUpdates { get; } + void ResetIgnoredUpdates(); + } + + [Export(typeof(IUpdateService))] + sealed class UpdateService : IUpdateService { + static readonly Guid SETTINGS_GUID = new Guid("611A864B-FD4E-4505-8C5F-0165CD09B77A"); + const string IGNORED_SECTION = "Ignored"; + const string IGNORED_ATTR = "id"; + + public bool CheckForUpdatesOnStartup { + get => checkForUpdatesOnStartup; + set { + if (checkForUpdatesOnStartup != value) { + checkForUpdatesOnStartup = value; + SaveSettings(); + } + } + } + bool checkForUpdatesOnStartup; + + public bool CanResetIgnoredUpdates => ignoredVersions.Count > 0; + + readonly ISettingsService settingsService; + readonly HashSet ignoredVersions; + + [ImportingConstructor] + public UpdateService(ISettingsService settingsService) { + this.settingsService = settingsService; + ignoredVersions = new HashSet(); + + var sect = settingsService.GetOrCreateSection(SETTINGS_GUID); + checkForUpdatesOnStartup = sect.Attribute(nameof(CheckForUpdatesOnStartup)) ?? true; + + foreach (var ignoredSect in sect.SectionsWithName(IGNORED_SECTION)) { + var versionString = ignoredSect.Attribute(IGNORED_ATTR); + if (!Version.TryParse(versionString, out var version)) + continue; + ignoredVersions.Add(version); + } + } + + static readonly Version currentVersion = GetCurrentVersion(); + + static Version GetCurrentVersion() { + var currentAsm = typeof(StartUpClass).Assembly; + try { + var fileVer = FileVersionInfo.GetVersionInfo(currentAsm.Location).FileVersion; + if (!string2.IsNullOrEmpty(fileVer)) + return new Version(fileVer); + } + catch { + } + return currentAsm.GetName().Version!; + } + + public async Task CheckForUpdatesAsync() { + var updateInfo = await TryGetLatestVersionAsync(); + if (updateInfo is not null) { + if (updateInfo.Value.Version > currentVersion) + return new UpdateCheckInfo(true, updateInfo.Value); + return new UpdateCheckInfo(false, default); + } + + return default; + } + + static async Task TryGetLatestVersionAsync() { + try { + string result; + using (var client = new HttpClient(new HttpClientHandler { UseProxy = true, UseDefaultCredentials = true })) { + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("dnSpyEx", currentVersion.ToString())); + + result = await client.GetStringAsync("https://api.github.com/repos/dnSpyEx/dnSpy/releases/latest"); + } + + var json = JObject.Parse(result); + if (!json.TryGetValue("tag_name", out var tagToken) || tagToken is not JValue tagValue || tagValue.Value is not string tagName) + return null; + if (!json.TryGetValue("html_url", out var urlToken) || urlToken is not JValue urlValue || urlValue.Value is not string htmlUrl) + return null; + if (tagName[0] == 'v') + tagName = tagName.Remove(0, 1); + if (Version.TryParse(tagName, out var version)) + return new VersionInfo(version, htmlUrl); + } + catch { + } + return null; + } + + public void MarkUpdateAsIgnored(Version version) { + ignoredVersions.Add(version); + SaveSettings(); + } + + void SaveSettings() { + var sect = settingsService.RecreateSection(SETTINGS_GUID); + sect.Attribute(nameof(CheckForUpdatesOnStartup), CheckForUpdatesOnStartup); + foreach (var version in ignoredVersions) { + var ignoredSect = sect.CreateSection(IGNORED_SECTION); + ignoredSect.Attribute(IGNORED_ATTR, version); + } + } + + public bool IsUpdateIgnored(Version version) => ignoredVersions.Contains(version); + + public void ResetIgnoredUpdates() { + ignoredVersions.Clear(); + SaveSettings(); + } + } +} diff --git a/dnSpy/dnSpy/Properties/dnSpy.Resources.Designer.cs b/dnSpy/dnSpy/Properties/dnSpy.Resources.Designer.cs index 23b89f76b7..1a53d78bb7 100644 --- a/dnSpy/dnSpy/Properties/dnSpy.Resources.Designer.cs +++ b/dnSpy/dnSpy/Properties/dnSpy.Resources.Designer.cs @@ -2908,6 +2908,33 @@ public static string IncrementalSearchCommand { } } + /// + /// Looks up a localized string similar to Ignore this update. + /// + public static string InfoBar_IgnoreThisUpdate { + get { + return ResourceManager.GetString("InfoBar_IgnoreThisUpdate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A new version of dnSpy is available: {0}. + /// + public static string InfoBar_NewUpdateAvailable { + get { + return ResourceManager.GetString("InfoBar_NewUpdateAvailable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open download page. + /// + public static string InfoBar_OpenDownloadPage { + get { + return ResourceManager.GetString("InfoBar_OpenDownloadPage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Integrate with Windows Explorer. /// @@ -3691,6 +3718,24 @@ public static string Options_Misc_Button_EnableAllWarnings { } } + /// + /// Looks up a localized string similar to Reset ignored updates. + /// + public static string Options_Misc_Button_ResetIgnoredUpdates { + get { + return ResourceManager.GetString("Options_Misc_Button_ResetIgnoredUpdates", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Check for updates on startup. + /// + public static string Options_Misc_CheckForUpdatesOnStartup { + get { + return ResourceManager.GetString("Options_Misc_CheckForUpdatesOnStartup", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use memory mapped I/O. /// diff --git a/dnSpy/dnSpy/Properties/dnSpy.Resources.resx b/dnSpy/dnSpy/Properties/dnSpy.Resources.resx index 0e198a1212..472c982637 100644 --- a/dnSpy/dnSpy/Properties/dnSpy.Resources.resx +++ b/dnSpy/dnSpy/Properties/dnSpy.Resources.resx @@ -483,9 +483,15 @@ Options + + Check for updates on startup + Enable all warning messages + + Reset ignored updates + Use memory mapped I/O @@ -1100,6 +1106,15 @@ Incremental Search + + A new version of dnSpy is available: {0} + + + Open download page + + + Ignore this update + Ctrl+I diff --git a/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml b/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml index 44ab2472fa..51fd23d3ee 100644 --- a/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml +++ b/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml @@ -262,9 +262,9 @@ - @@ -274,22 +274,22 @@ - - - - - @@ -864,7 +864,7 @@ - + - - + + - +