diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs index b8bfee591e6..a82cae5d254 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 @@ public Task HttpGetStreamAsync(string url, CancellationToken token = def 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 @@ public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null _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 14b8eef4e16..0b3f2be6557 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -83,15 +83,50 @@ var userName when string.IsNullOrEmpty(userName) => } } - 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.CreateNew, 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 a0186b7a214..8376fd07ba7 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin.SharedModels; +using Flow.Launcher.Plugin.SharedModels; using JetBrains.Annotations; using System; using System.Collections.Generic; @@ -181,9 +181,13 @@ public interface IPublicAPI /// /// 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 @@ -316,5 +320,19 @@ public interface IPublicAPI /// Specifies the default result of the message box. /// Specifies which message box button is clicked by the user. public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK); + + /// + /// Displays a standardised Flow message box. + /// If there is issue when showing the message box, it will return null. + /// + /// The caption of the message box. + /// + /// Time-consuming task function, whose input is the action to report progress. + /// The input of the action is the progress value which is a double value between 0 and 100. + /// If there are any exceptions, this action will be null. + /// + /// When user closes the progress box manually by button or esc key, this action will be called. + /// A progress box interface. + public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, Action forceClosed = null); } } diff --git a/Flow.Launcher/ProgressBoxEx.xaml b/Flow.Launcher/ProgressBoxEx.xaml new file mode 100644 index 00000000000..3102cfb7255 --- /dev/null +++ b/Flow.Launcher/ProgressBoxEx.xaml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +