From 4b8bef4c04d6bb4c1993fb9e34aab7b4c97ac2af Mon Sep 17 00:00:00 2001 From: Etienne Baudoux Date: Sun, 14 Jul 2024 20:06:23 -0700 Subject: [PATCH] Added check for update for extensions --- .../ExtensionInstallationManager.cs | 56 +++++++++++ .../ExtensionsManagerGuiTool.cs | 40 +++++++- .../SupportDevelopmentGuidTools.cs | 2 +- src/app/dev/DevToys.Blazor/Pages/Index.razor | 15 +++ .../dev/DevToys.Blazor/Pages/Index.razor.cs | 22 ++++- .../ViewModels/MainWindowViewModel.cs | 93 +++++++++++-------- .../Strings/MainWindow/MainWindow.Designer.cs | 9 ++ .../Strings/MainWindow/MainWindow.resx | 3 + 8 files changed, 198 insertions(+), 42 deletions(-) diff --git a/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionInstallationManager.cs b/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionInstallationManager.cs index 78d4120d29..cedab61cd9 100644 --- a/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionInstallationManager.cs +++ b/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionInstallationManager.cs @@ -1,5 +1,7 @@ using System.Runtime.InteropServices; +using DevToys.Core.Web; using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; using NuGet.Packaging; namespace DevToys.Blazor.BuiltInTools.ExtensionsManager; @@ -117,6 +119,60 @@ string extensionInstallationPath return new(AlreadyInstalled: false, nuspecReader, extensionInstallationPath); } + internal static async Task CheckForAnyUpdateAsync(IWebClientService webClientService) + { + + await TaskSchedulerAwaiter.SwitchOffMainThreadAsync(CancellationToken.None); + + var updateCheckTasks = new List>(); + + for (int i = 0; i < ExtensionInstallationFolders.Length; i++) + { + if (Directory.Exists(ExtensionInstallationFolders[i])) + { + IEnumerable nuspecFiles + = Directory.EnumerateFiles(ExtensionInstallationFolders[i], "*.nuspec", SearchOption.AllDirectories); + + foreach (string nuspecFile in nuspecFiles) + { + var nuspec = new NuspecReader(nuspecFile); + updateCheckTasks.Add(CheckForUpdateAsync(webClientService, nuspec)); + } + } + } + + await Task.WhenAll(updateCheckTasks); + + return updateCheckTasks.Any(t => t.Result); + } + + internal static async Task CheckForUpdateAsync(IWebClientService webClientService, NuspecReader nuspec) + { + const string NuGetOrgVersionUrl = "https://api.nuget.org/v3-flatcontainer/{0}/index.json"; + + await TaskSchedulerAwaiter.SwitchOffMainThreadAsync(CancellationToken.None); + + string url = string.Format(NuGetOrgVersionUrl, nuspec.GetId()); + + string? response = await webClientService.SafeGetStringAsync(new Uri(url), CancellationToken.None); + if (response is not null) + { + var jObject = JObject.Parse(response); + if (jObject is not null) + { + IEnumerable? versions = jObject["versions"]?.Values(); + string? latestVersion = versions?.LastOrDefault(); + + if (!string.Equals(latestVersion, nuspec.GetVersion().OriginalVersion, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + + return false; + } + private static IEnumerable GetPathToExclude() { if (OperatingSystem.IsWindows()) diff --git a/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionsManagerGuiTool.cs b/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionsManagerGuiTool.cs index 9b4b47f283..9daa37af45 100644 --- a/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionsManagerGuiTool.cs +++ b/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionsManagerGuiTool.cs @@ -1,13 +1,16 @@ using System.Collections.Immutable; +using DevToys.Api; using DevToys.Blazor.BuiltInTools.Settings; using DevToys.Core; +using DevToys.Core.Settings; +using DevToys.Core.Web; using NuGet.Packaging; using NuGet.Packaging.Core; namespace DevToys.Blazor.BuiltInTools.ExtensionsManager; [Export(typeof(IGuiTool))] -[Name(ExtensionmanagerToolName)] +[Name(ExtensionManagerToolName)] [ToolDisplayInformation( IconFontName = "FluentSystemIcons", IconGlyph = '\uE9E8', @@ -26,7 +29,7 @@ namespace DevToys.Blazor.BuiltInTools.ExtensionsManager; [TargetPlatform(Platform.MacOS)] internal sealed class ExtensionsManagerGuiTool : IGuiTool { - internal const string ExtensionmanagerToolName = "Extensions Manager"; + internal const string ExtensionManagerToolName = "Extensions Manager"; private enum GridRows { @@ -52,6 +55,12 @@ private enum GridColumns #pragma warning disable IDE0044 // Add readonly modifier [Import] private IFileStorage _fileStorage = default!; + + [Import] + private IWebClientService _webClientService = default!; + + [Import] + private ISettingsProvider _settingsProvider = default!; #pragma warning restore IDE0044 // Add readonly modifier public UIToolView View @@ -169,6 +178,11 @@ private void OnUninstallExtensionButtonClick(string extensionInstallationPath) _extensionList.Items.RemoveValue(extensionInstallationPath); } + private void OnUpdateExtensionButtonClick(NuspecReader nuspec) + { + OSHelper.OpenFileInShell(string.Format("https://www.nuget.org/packages/{0}", nuspec.GetId())); + } + private async Task LoadExtensionListAsync() { _extensionList.Items.Clear(); @@ -229,6 +243,26 @@ IUIButton uninstallButton .Icon("FluentSystemIcons", '\uE47B') .OnClick(() => OnUninstallExtensionButtonClick(extensionInstallationPath)); actionBuilder.Add(uninstallButton); + IUIStack actionStack = Stack(); + + if (_settingsProvider.GetSetting(PredefinedSettings.CheckForUpdate)) + { + Task.Run(async () => + { + bool updateAvailable = await ExtensionInstallationManager.CheckForUpdateAsync(_webClientService, nuspec); + if (updateAvailable) + { + // Add update button. + IUIButton updateButton + = Button() + .Icon("FluentSystemIcons", '\uF150') + .HyperlinkAppearance() + .OnClick(() => OnUpdateExtensionButtonClick(nuspec)); + actionBuilder.Insert(0, updateButton); + actionStack.WithChildren(actionBuilder.ToArray()); + } + }).ForgetSafely(); + } // Create the item. return Item( @@ -242,7 +276,7 @@ IUIButton uninstallButton .MediumSpacing() .WithChildren( Label().Text(SizeWithUnit(size)), - Stack() + actionStack .Horizontal() .SmallSpacing() .WithChildren(actionBuilder.ToArray()))), diff --git a/src/app/dev/DevToys.Blazor/BuiltInTools/SupportDevelopment/SupportDevelopmentGuidTools.cs b/src/app/dev/DevToys.Blazor/BuiltInTools/SupportDevelopment/SupportDevelopmentGuidTools.cs index fc51c45604..a22f6b7bd5 100644 --- a/src/app/dev/DevToys.Blazor/BuiltInTools/SupportDevelopment/SupportDevelopmentGuidTools.cs +++ b/src/app/dev/DevToys.Blazor/BuiltInTools/SupportDevelopment/SupportDevelopmentGuidTools.cs @@ -16,7 +16,7 @@ namespace DevToys.Blazor.BuiltInTools.SupportDevelopment; [NotFavorable] [NotSearchable] [NoCompactOverlaySupport] -[Order(Before = ExtensionsManagerGuiTool.ExtensionmanagerToolName)] +[Order(Before = ExtensionsManagerGuiTool.ExtensionManagerToolName)] internal sealed class SupportDevelopmentGuidTools : IGuiTool { // TODO: Finish this tool. diff --git a/src/app/dev/DevToys.Blazor/Pages/Index.razor b/src/app/dev/DevToys.Blazor/Pages/Index.razor index 4dd65cbb59..dacf69e7a5 100644 --- a/src/app/dev/DevToys.Blazor/Pages/Index.razor +++ b/src/app/dev/DevToys.Blazor/Pages/Index.razor @@ -174,6 +174,21 @@