Skip to content

Commit

Permalink
Improve HttpDownloadAsync function & Use it in PluginManager plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack251970 committed Jan 11, 2025
1 parent 029cb38 commit 8eb5a4d
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 72 deletions.
7 changes: 2 additions & 5 deletions Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -121,10 +120,10 @@ public Task<Stream> 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<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)
Expand Down Expand Up @@ -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);
Expand Down
41 changes: 38 additions & 3 deletions Flow.Launcher.Infrastructure/Http/Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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
{
Expand Down
6 changes: 5 additions & 1 deletion Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,13 @@ public interface IPublicAPI
/// </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
Expand Down
4 changes: 2 additions & 2 deletions Flow.Launcher/PublicAPIInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ public MatchResult FuzzySearch(string query, string stringToCompare) =>
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);
Expand Down
82 changes: 21 additions & 61 deletions Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -144,83 +142,45 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin)

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

// 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);
}
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 && (!cts.IsCancellationRequested))
await Http.DownloadAsync(plugin.UrlDownload, filePath, null, cts.Token).ConfigureAwait(false);
}
else
{
filePath = plugin.LocalInstallPath;
}

// check if user cancelled download before installing plugin
if (downloadCancelled)
if (cts.IsCancellationRequested)
return;
else
Install(plugin, filePath);
Expand Down

0 comments on commit 8eb5a4d

Please sign in to comment.