Skip to content

Commit 741ddfe

Browse files
authored
Merge pull request #3170 from Jack251970/dev3
Add New API for ProgressBoxEx to Show Progress & Add Progress Display for Plugin Downloading & Improve DownloadUrl Api Function
2 parents 418270f + 646bad6 commit 741ddfe

File tree

8 files changed

+382
-59
lines changed

8 files changed

+382
-59
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.CreateNew, 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: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Flow.Launcher.Plugin.SharedModels;
1+
using Flow.Launcher.Plugin.SharedModels;
22
using JetBrains.Annotations;
33
using System;
44
using System.Collections.Generic;
@@ -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
@@ -316,5 +320,19 @@ public interface IPublicAPI
316320
/// <param name="defaultResult">Specifies the default result of the message box.</param>
317321
/// <returns>Specifies which message box button is clicked by the user.</returns>
318322
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK);
323+
324+
/// <summary>
325+
/// Displays a standardised Flow message box.
326+
/// If there is issue when showing the message box, it will return null.
327+
/// </summary>
328+
/// <param name="caption">The caption of the message box.</param>
329+
/// <param name="reportProgressAsync">
330+
/// Time-consuming task function, whose input is the action to report progress.
331+
/// The input of the action is the progress value which is a double value between 0 and 100.
332+
/// If there are any exceptions, this action will be null.
333+
/// </param>
334+
/// <param name="forceClosed">When user closes the progress box manually by button or esc key, this action will be called.</param>
335+
/// <returns>A progress box interface.</returns>
336+
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action forceClosed = null);
319337
}
320338
}

Flow.Launcher/ProgressBoxEx.xaml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<Window
2+
x:Class="Flow.Launcher.ProgressBoxEx"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:local="clr-namespace:Flow.Launcher"
7+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
8+
x:Name="MessageBoxWindow"
9+
Width="420"
10+
Height="Auto"
11+
Background="{DynamicResource PopuBGColor}"
12+
Foreground="{DynamicResource PopupTextColor}"
13+
ResizeMode="NoResize"
14+
SizeToContent="Height"
15+
WindowStartupLocation="CenterScreen"
16+
mc:Ignorable="d">
17+
<WindowChrome.WindowChrome>
18+
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
19+
</WindowChrome.WindowChrome>
20+
<Window.InputBindings>
21+
<KeyBinding Key="Escape" Command="Close" />
22+
</Window.InputBindings>
23+
<Window.CommandBindings>
24+
<CommandBinding Command="Close" Executed="KeyEsc_OnPress" />
25+
</Window.CommandBindings>
26+
<Grid>
27+
<Grid.RowDefinitions>
28+
<RowDefinition Height="Auto" />
29+
<RowDefinition />
30+
<RowDefinition MinHeight="68" />
31+
</Grid.RowDefinitions>
32+
<StackPanel Grid.Row="0">
33+
<StackPanel>
34+
<Grid>
35+
<Grid.ColumnDefinitions>
36+
<ColumnDefinition Width="*" />
37+
<ColumnDefinition Width="Auto" />
38+
</Grid.ColumnDefinitions>
39+
<Button
40+
Grid.Column="1"
41+
Click="Button_Cancel"
42+
Style="{StaticResource TitleBarCloseButtonStyle}">
43+
<Path
44+
Width="46"
45+
Height="32"
46+
Data="M 18,11 27,20 M 18,20 27,11"
47+
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
48+
StrokeThickness="1">
49+
<Path.Style>
50+
<Style TargetType="Path">
51+
<Style.Triggers>
52+
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
53+
<Setter Property="Opacity" Value="0.5" />
54+
</DataTrigger>
55+
</Style.Triggers>
56+
</Style>
57+
</Path.Style>
58+
</Path>
59+
</Button>
60+
</Grid>
61+
</StackPanel>
62+
</StackPanel>
63+
<Grid Grid.Row="1" Margin="30 0 30 24">
64+
<Grid.RowDefinitions>
65+
<RowDefinition Height="Auto" />
66+
<RowDefinition Height="Auto" />
67+
</Grid.RowDefinitions>
68+
<TextBlock
69+
x:Name="TitleTextBlock"
70+
Grid.Row="0"
71+
MaxWidth="400"
72+
Margin="0 0 26 12"
73+
VerticalAlignment="Center"
74+
FontFamily="Segoe UI"
75+
FontSize="20"
76+
FontWeight="SemiBold"
77+
TextAlignment="Left"
78+
TextWrapping="Wrap" />
79+
<ProgressBar
80+
x:Name="ProgressBar"
81+
Grid.Row="1"
82+
Margin="0 0 26 0"
83+
Maximum="100"
84+
Minimum="0"
85+
Value="0" />
86+
</Grid>
87+
<Border
88+
Grid.Row="2"
89+
Margin="0 0 0 0"
90+
Background="{DynamicResource PopupButtonAreaBGColor}"
91+
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
92+
BorderThickness="0 1 0 0">
93+
<WrapPanel
94+
HorizontalAlignment="Center"
95+
VerticalAlignment="Center"
96+
Orientation="Horizontal">
97+
<Button
98+
x:Name="btnCancel"
99+
MinWidth="120"
100+
Margin="5 0 5 0"
101+
Click="Button_Click"
102+
Content="{DynamicResource commonCancel}" />
103+
</WrapPanel>
104+
</Border>
105+
</Grid>
106+
</Window>

Flow.Launcher/ProgressBoxEx.xaml.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using System.Windows;
4+
using System.Windows.Input;
5+
using Flow.Launcher.Infrastructure.Logger;
6+
7+
namespace Flow.Launcher
8+
{
9+
public partial class ProgressBoxEx : Window
10+
{
11+
private readonly Action _forceClosed;
12+
13+
private ProgressBoxEx(Action forceClosed)
14+
{
15+
_forceClosed = forceClosed;
16+
InitializeComponent();
17+
}
18+
19+
public static async Task ShowAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action forceClosed = null)
20+
{
21+
ProgressBoxEx prgBox = null;
22+
try
23+
{
24+
if (!Application.Current.Dispatcher.CheckAccess())
25+
{
26+
await Application.Current.Dispatcher.InvokeAsync(() =>
27+
{
28+
prgBox = new ProgressBoxEx(forceClosed)
29+
{
30+
Title = caption
31+
};
32+
prgBox.TitleTextBlock.Text = caption;
33+
prgBox.Show();
34+
});
35+
}
36+
else
37+
{
38+
prgBox = new ProgressBoxEx(forceClosed)
39+
{
40+
Title = caption
41+
};
42+
prgBox.TitleTextBlock.Text = caption;
43+
prgBox.Show();
44+
}
45+
46+
await reportProgressAsync(prgBox.ReportProgress).ConfigureAwait(false);
47+
}
48+
catch (Exception e)
49+
{
50+
Log.Error($"|ProgressBoxEx.Show|An error occurred: {e.Message}");
51+
52+
await reportProgressAsync(null).ConfigureAwait(false);
53+
}
54+
finally
55+
{
56+
if (!Application.Current.Dispatcher.CheckAccess())
57+
{
58+
await Application.Current.Dispatcher.InvokeAsync(() =>
59+
{
60+
prgBox?.Close();
61+
});
62+
}
63+
else
64+
{
65+
prgBox?.Close();
66+
}
67+
}
68+
}
69+
70+
private void ReportProgress(double progress)
71+
{
72+
if (!Application.Current.Dispatcher.CheckAccess())
73+
{
74+
Application.Current.Dispatcher.Invoke(() => ReportProgress(progress));
75+
return;
76+
}
77+
78+
if (progress < 0)
79+
{
80+
ProgressBar.Value = 0;
81+
}
82+
else if (progress >= 100)
83+
{
84+
ProgressBar.Value = 100;
85+
Close();
86+
}
87+
else
88+
{
89+
ProgressBar.Value = progress;
90+
}
91+
}
92+
93+
private void KeyEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
94+
{
95+
ForceClose();
96+
}
97+
98+
private void Button_Click(object sender, RoutedEventArgs e)
99+
{
100+
ForceClose();
101+
}
102+
103+
private void Button_Cancel(object sender, RoutedEventArgs e)
104+
{
105+
ForceClose();
106+
}
107+
108+
private void ForceClose()
109+
{
110+
Close();
111+
_forceClosed?.Invoke();
112+
}
113+
}
114+
}

Flow.Launcher/PublicAPIInstance.cs

Lines changed: 4 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);
@@ -324,6 +324,8 @@ public bool IsGameModeOn()
324324
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK) =>
325325
MessageBoxEx.Show(messageBoxText, caption, button, icon, defaultResult);
326326

327+
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action forceClosed = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, forceClosed);
328+
327329
#endregion
328330

329331
#region Private Methods

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
5151
return query.FirstSearch.ToLower() switch
5252
{
5353
//search could be url, no need ToLower() when passed in
54-
Settings.InstallCommand => await pluginManager.RequestInstallOrUpdate(query.SecondToEndSearch, token, query.IsReQuery),
54+
Settings.InstallCommand => await pluginManager.RequestInstallOrUpdateAsync(query.SecondToEndSearch, token, query.IsReQuery),
5555
Settings.UninstallCommand => pluginManager.RequestUninstall(query.SecondToEndSearch),
5656
Settings.UpdateCommand => await pluginManager.RequestUpdateAsync(query.SecondToEndSearch, token, query.IsReQuery),
5757
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>

0 commit comments

Comments
 (0)