Improve HttpDownloadAsync function & Use it in PluginManager plugin

This commit is contained in:
Jack251970 2025-01-11 13:24:41 +08:00
parent 029cb38c61
commit 8eb5a4dfca
5 changed files with 67 additions and 71 deletions

View file

@ -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<double> 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);

View file

@ -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<double> 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
{

View file

@ -181,9 +181,13 @@ namespace Flow.Launcher.Plugin
/// </summary>
/// <param name="url">URL to download file</param>
/// <param name="filePath">path to save downloaded file</param>
/// <param name="reportProgress">
/// 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.
/// </param>
/// <param name="token">place to store file</param>
/// <returns>Task showing the progress</returns>
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default);
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null, CancellationToken token = default);
/// <summary>
/// Add ActionKeyword for specific plugin

View file

@ -164,8 +164,8 @@ namespace Flow.Launcher
public Task<Stream> 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<double> reportProgress = null,
CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token);
public void AddActionKeyword(string pluginId, string newActionKeyword) =>
PluginManager.AddActionKeyword(pluginId, newActionKeyword);

View file

@ -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);