Skip to content

Commit 8eb5a4d

Browse files
committed
Improve HttpDownloadAsync function & Use it in PluginManager plugin
1 parent 029cb38 commit 8eb5a4d

File tree

5 files changed

+68
-72
lines changed

5 files changed

+68
-72
lines changed

Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.ComponentModel;
43
using System.Diagnostics.CodeAnalysis;
54
using System.IO;
65
using System.Runtime.CompilerServices;
@@ -121,10 +120,10 @@ public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = def
121120
return _api.HttpGetStreamAsync(url, token);
122121
}
123122

124-
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath,
123+
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null,
125124
CancellationToken token = default)
126125
{
127-
return _api.HttpDownloadAsync(url, filePath, token);
126+
return _api.HttpDownloadAsync(url, filePath, reportProgress, token);
128127
}
129128

130129
public void AddActionKeyword(string pluginId, string newActionKeyword)
@@ -162,13 +161,11 @@ public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null
162161
_api.OpenDirectory(DirectoryPath, FileNameOrFilePath);
163162
}
164163

165-
166164
public void OpenUrl(string url, bool? inPrivate = null)
167165
{
168166
_api.OpenUrl(url, inPrivate);
169167
}
170168

171-
172169
public void OpenAppUri(string appUri)
173170
{
174171
_api.OpenAppUri(appUri);

Flow.Launcher.Infrastructure/Http/Http.cs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,50 @@ var userName when string.IsNullOrEmpty(userName) =>
8383
}
8484
}
8585

86-
public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default)
86+
public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null, CancellationToken token = default)
8787
{
8888
try
8989
{
9090
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
91+
9192
if (response.StatusCode == HttpStatusCode.OK)
9293
{
93-
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
94-
await response.Content.CopyToAsync(fileStream, token);
94+
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
95+
var canReportProgress = totalBytes != -1;
96+
97+
if (canReportProgress && reportProgress != null)
98+
{
99+
await using var contentStream = await response.Content.ReadAsStreamAsync(token);
100+
await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
101+
102+
var buffer = new byte[8192];
103+
long totalRead = 0;
104+
int read;
105+
double progressValue = 0;
106+
107+
reportProgress(0);
108+
109+
while ((read = await contentStream.ReadAsync(buffer, token)) > 0)
110+
{
111+
await fileStream.WriteAsync(buffer.AsMemory(0, read), token);
112+
totalRead += read;
113+
114+
progressValue = totalRead * 100.0 / totalBytes;
115+
116+
if (token.IsCancellationRequested)
117+
return;
118+
else
119+
reportProgress(progressValue);
120+
}
121+
122+
if (progressValue < 100)
123+
reportProgress(100);
124+
}
125+
else
126+
{
127+
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
128+
await response.Content.CopyToAsync(fileStream, token);
129+
}
95130
}
96131
else
97132
{

Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,13 @@ public interface IPublicAPI
181181
/// </summary>
182182
/// <param name="url">URL to download file</param>
183183
/// <param name="filePath">path to save downloaded file</param>
184+
/// <param name="reportProgress">
185+
/// Action to report progress. The input of the action is the progress value which is a double value between 0 and 100.
186+
/// It will be called if url support range request and the reportProgress is not null.
187+
/// </param>
184188
/// <param name="token">place to store file</param>
185189
/// <returns>Task showing the progress</returns>
186-
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default);
190+
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null, CancellationToken token = default);
187191

188192
/// <summary>
189193
/// Add ActionKeyword for specific plugin

Flow.Launcher/PublicAPIInstance.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ public MatchResult FuzzySearch(string query, string stringToCompare) =>
164164
public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default) =>
165165
Http.GetStreamAsync(url);
166166

167-
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath,
168-
CancellationToken token = default) => Http.DownloadAsync(url, filePath, token);
167+
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null,
168+
CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token);
169169

170170
public void AddActionKeyword(string pluginId, string newActionKeyword) =>
171171
PluginManager.AddActionKeyword(pluginId, newActionKeyword);

Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs

Lines changed: 21 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
1717
{
1818
internal class PluginsManager
1919
{
20-
private static readonly HttpClient HttpClient = new();
21-
2220
private const string zip = "zip";
2321

2422
private PluginInitContext Context { get; set; }
@@ -144,83 +142,45 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin)
144142

145143
var filePath = Path.Combine(Path.GetTempPath(), downloadFilename);
146144

147-
var downloadCancelled = false;
148145
var exceptionHappened = false;
149146
try
150147
{
148+
using var cts = new CancellationTokenSource();
149+
151150
if (!plugin.IsFromLocalInstallPath)
152151
{
153152
if (File.Exists(filePath))
154153
File.Delete(filePath);
155154

156-
using var cts = new CancellationTokenSource();
157-
using var response = await HttpClient.GetAsync(plugin.UrlDownload, HttpCompletionOption.ResponseHeadersRead, cts.Token).ConfigureAwait(false);
158-
159-
response.EnsureSuccessStatusCode();
160-
161-
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
162-
var canReportProgress = totalBytes != -1;
163-
164155
var prgBoxTitle = $"{Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin")} {plugin.Name}";
165-
if (canReportProgress)
166-
{
167-
await Context.API.ShowProgressBoxAsync(prgBoxTitle,
168-
async (reportProgress) =>
156+
await Context.API.ShowProgressBoxAsync(prgBoxTitle,
157+
async (reportProgress) =>
158+
{
159+
if (reportProgress == null)
169160
{
170-
if (reportProgress == null)
171-
{
172-
// when reportProgress is null, it means there is expcetion with the progress box
173-
// so we record it with exceptionHappened and return so that progress box will close instantly
174-
exceptionHappened = true;
175-
return;
176-
}
177-
else
178-
{
179-
await using var contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
180-
await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
181-
182-
var buffer = new byte[8192];
183-
long totalRead = 0;
184-
int read;
185-
186-
while ((read = await contentStream.ReadAsync(buffer).ConfigureAwait(false)) > 0)
187-
{
188-
await fileStream.WriteAsync(buffer.AsMemory(0, read)).ConfigureAwait(false);
189-
totalRead += read;
190-
191-
var progressValue = totalRead * 100 / totalBytes;
192-
193-
// check if user cancelled download before reporting progress
194-
if (downloadCancelled)
195-
return;
196-
else
197-
reportProgress(progressValue);
198-
}
199-
}
200-
},
201-
() =>
161+
// when reportProgress is null, it means there is expcetion with the progress box
162+
// so we record it with exceptionHappened and return so that progress box will close instantly
163+
exceptionHappened = true;
164+
return;
165+
}
166+
else
202167
{
203-
cts.Cancel();
204-
downloadCancelled = true;
205-
});
206-
207-
// if exception happened while downloading and user does not cancel downloading,
208-
// we need to redownload the plugin
209-
if (exceptionHappened && (!downloadCancelled))
210-
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
211-
}
212-
else
213-
{
214-
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
215-
}
168+
await Http.DownloadAsync(plugin.UrlDownload, filePath, reportProgress, cts.Token).ConfigureAwait(false);
169+
}
170+
}, cts.Cancel);
171+
172+
// if exception happened while downloading and user does not cancel downloading,
173+
// we need to redownload the plugin
174+
if (exceptionHappened && (!cts.IsCancellationRequested))
175+
await Http.DownloadAsync(plugin.UrlDownload, filePath, null, cts.Token).ConfigureAwait(false);
216176
}
217177
else
218178
{
219179
filePath = plugin.LocalInstallPath;
220180
}
221181

222182
// check if user cancelled download before installing plugin
223-
if (downloadCancelled)
183+
if (cts.IsCancellationRequested)
224184
return;
225185
else
226186
Install(plugin, filePath);

0 commit comments

Comments
 (0)