From 8eb5a4dfcaa063d647f31e01211a4ef947344849 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 11 Jan 2025 13:24:41 +0800 Subject: [PATCH] Improve HttpDownloadAsync function & Use it in PluginManager plugin --- .../JsonRPCV2Models/JsonRPCPublicAPI.cs | 7 +- Flow.Launcher.Infrastructure/Http/Http.cs | 41 +++++++++- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 6 +- Flow.Launcher/PublicAPIInstance.cs | 4 +- .../PluginsManager.cs | 80 +++++-------------- 5 files changed, 67 insertions(+), 71 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs index b8bfee591..a82cae5d2 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.CompilerServices; @@ -121,10 +120,10 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models return _api.HttpGetStreamAsync(url, token); } - public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, + public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action reportProgress = null, CancellationToken token = default) { - return _api.HttpDownloadAsync(url, filePath, token); + return _api.HttpDownloadAsync(url, filePath, reportProgress, token); } public void AddActionKeyword(string pluginId, string newActionKeyword) @@ -162,13 +161,11 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models _api.OpenDirectory(DirectoryPath, FileNameOrFilePath); } - public void OpenUrl(string url, bool? inPrivate = null) { _api.OpenUrl(url, inPrivate); } - public void OpenAppUri(string appUri) { _api.OpenAppUri(appUri); diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 14b8eef4e..0b5d1b05a 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -83,15 +83,50 @@ namespace Flow.Launcher.Infrastructure.Http } } - public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default) + public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, Action reportProgress = null, CancellationToken token = default) { try { using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); + if (response.StatusCode == HttpStatusCode.OK) { - await using var fileStream = new FileStream(filePath, FileMode.CreateNew); - await response.Content.CopyToAsync(fileStream, token); + var totalBytes = response.Content.Headers.ContentLength ?? -1L; + var canReportProgress = totalBytes != -1; + + if (canReportProgress && reportProgress != null) + { + await using var contentStream = await response.Content.ReadAsStreamAsync(token); + await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); + + var buffer = new byte[8192]; + long totalRead = 0; + int read; + double progressValue = 0; + + reportProgress(0); + + while ((read = await contentStream.ReadAsync(buffer, token)) > 0) + { + await fileStream.WriteAsync(buffer.AsMemory(0, read), token); + totalRead += read; + + progressValue = totalRead * 100.0 / totalBytes; + + if (token.IsCancellationRequested) + return; + else + reportProgress(progressValue); + } + + if (progressValue < 100) + reportProgress(100); + } + else + { + await using var fileStream = new FileStream(filePath, FileMode.CreateNew); + await response.Content.CopyToAsync(fileStream, token); + } } else { diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index c4bfd7033..8376fd07b 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -181,9 +181,13 @@ namespace Flow.Launcher.Plugin /// /// URL to download file /// path to save downloaded file + /// + /// Action to report progress. The input of the action is the progress value which is a double value between 0 and 100. + /// It will be called if url support range request and the reportProgress is not null. + /// /// place to store file /// Task showing the progress - Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default); + Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action reportProgress = null, CancellationToken token = default); /// /// Add ActionKeyword for specific plugin diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 95d371fb0..7706a64ba 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -164,8 +164,8 @@ namespace Flow.Launcher public Task HttpGetStreamAsync(string url, CancellationToken token = default) => Http.GetStreamAsync(url); - public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, - CancellationToken token = default) => Http.DownloadAsync(url, filePath, token); + public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action reportProgress = null, + CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token); public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword); diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index b6495d8f2..aee76e65e 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -17,8 +17,6 @@ namespace Flow.Launcher.Plugin.PluginsManager { internal class PluginsManager { - private static readonly HttpClient HttpClient = new(); - private const string zip = "zip"; private PluginInitContext Context { get; set; } @@ -144,75 +142,37 @@ namespace Flow.Launcher.Plugin.PluginsManager var filePath = Path.Combine(Path.GetTempPath(), downloadFilename); - var downloadCancelled = false; var exceptionHappened = false; try { + using var cts = new CancellationTokenSource(); + if (!plugin.IsFromLocalInstallPath) { if (File.Exists(filePath)) File.Delete(filePath); - using var cts = new CancellationTokenSource(); - using var response = await HttpClient.GetAsync(plugin.UrlDownload, HttpCompletionOption.ResponseHeadersRead, cts.Token).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - var totalBytes = response.Content.Headers.ContentLength ?? -1L; - var canReportProgress = totalBytes != -1; - var prgBoxTitle = $"{Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin")} {plugin.Name}"; - if (canReportProgress) - { - await Context.API.ShowProgressBoxAsync(prgBoxTitle, - async (reportProgress) => + await Context.API.ShowProgressBoxAsync(prgBoxTitle, + async (reportProgress) => + { + if (reportProgress == null) { - if (reportProgress == null) - { - // when reportProgress is null, it means there is expcetion with the progress box - // so we record it with exceptionHappened and return so that progress box will close instantly - exceptionHappened = true; - return; - } - else - { - await using var contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); - - var buffer = new byte[8192]; - long totalRead = 0; - int read; - - while ((read = await contentStream.ReadAsync(buffer).ConfigureAwait(false)) > 0) - { - await fileStream.WriteAsync(buffer.AsMemory(0, read)).ConfigureAwait(false); - totalRead += read; - - var progressValue = totalRead * 100 / totalBytes; - - // check if user cancelled download before reporting progress - if (downloadCancelled) - return; - else - reportProgress(progressValue); - } - } - }, - () => + // when reportProgress is null, it means there is expcetion with the progress box + // so we record it with exceptionHappened and return so that progress box will close instantly + exceptionHappened = true; + return; + } + else { - cts.Cancel(); - downloadCancelled = true; - }); + await Http.DownloadAsync(plugin.UrlDownload, filePath, reportProgress, cts.Token).ConfigureAwait(false); + } + }, cts.Cancel); - // if exception happened while downloading and user does not cancel downloading, - // we need to redownload the plugin - if (exceptionHappened && (!downloadCancelled)) - await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); - } - else - { - await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); - } + // if exception happened while downloading and user does not cancel downloading, + // we need to redownload the plugin + if (exceptionHappened && (!cts.IsCancellationRequested)) + await Http.DownloadAsync(plugin.UrlDownload, filePath, null, cts.Token).ConfigureAwait(false); } else { @@ -220,7 +180,7 @@ namespace Flow.Launcher.Plugin.PluginsManager } // check if user cancelled download before installing plugin - if (downloadCancelled) + if (cts.IsCancellationRequested) return; else Install(plugin, filePath);