Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add New API for ProgressBoxEx to Show Progress & Add Progress Display for Plugin Downloading & Improve DownloadUrl Api Function #3170

Open
wants to merge 28 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
37058f7
Improve code quality.
Jack251970 Jan 4, 2025
562b233
Add progress box support for downloading plugin
Jack251970 Jan 4, 2025
54f02e0
Use public api for ProgressBoxEx & Add support for force close event
Jack251970 Jan 4, 2025
d0bab86
Improve code quality
Jack251970 Jan 4, 2025
f69dd0f
Improve documents
Jack251970 Jan 4, 2025
c06ba59
Add support for cancelling download
Jack251970 Jan 5, 2025
07bb16c
Improve code quality
Jack251970 Jan 5, 2025
c907c29
Fix plugin install issue
Jack251970 Jan 5, 2025
675ee9e
Add translation for progress box title
Jack251970 Jan 6, 2025
0fabe31
Improve code quality
Jack251970 Jan 6, 2025
cc0fb66
Extract duplicate cleanup code into a method
Jack251970 Jan 6, 2025
bc84910
Use static HttpClient instance for heavy load issue
Jack251970 Jan 6, 2025
aff6b1a
Perserve prgBox value when force close
Jack251970 Jan 6, 2025
8aebf95
Check ui thread when calling close function & Update documents
Jack251970 Jan 9, 2025
122887e
Await close event from non-ui thread
Jack251970 Jan 9, 2025
6220b34
Fix dulplicated close event
Jack251970 Jan 9, 2025
5016334
Avoid cancelling all pending requests on shared HttpClient instance
Jack251970 Jan 9, 2025
381e64e
Move ProgressBoxEx to main project for better development experience
Jack251970 Jan 9, 2025
d36aef5
Change ProgressBoxEx namespace
Jack251970 Jan 9, 2025
ed7265d
Remove override close event
Jack251970 Jan 10, 2025
297d191
Use function to delegate the progress task
Jack251970 Jan 10, 2025
cc921c7
Improve progress box when exception happens
Jack251970 Jan 10, 2025
df3cb58
Improve code quality
Jack251970 Jan 11, 2025
029cb38
Fix progress box action under ui thread
Jack251970 Jan 11, 2025
8eb5a4d
Improve HttpDownloadAsync function & Use it in PluginManager plugin
Jack251970 Jan 11, 2025
1bf045f
Make fileMode usage between progress and non-progress paths consistent
Jack251970 Jan 19, 2025
fc2ce73
Merge branch 'dev' into dev3
Jack251970 Jan 19, 2025
c32435f
Use api to call download function & Add message box for all download …
Jack251970 Jan 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.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);
Jack251970 marked this conversation as resolved.
Show resolved Hide resolved
}

if (progressValue < 100)
reportProgress(100);
}
else
{
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
await response.Content.CopyToAsync(fileStream, token);
}
}
else
{
Expand Down
22 changes: 20 additions & 2 deletions Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Plugin.SharedModels;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
Expand Down 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 Expand Up @@ -316,5 +320,19 @@ public interface IPublicAPI
/// <param name="defaultResult">Specifies the default result of the message box.</param>
/// <returns>Specifies which message box button is clicked by the user.</returns>
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK);

/// <summary>
/// Displays a standardised Flow message box.
/// If there is issue when showing the message box, it will return null.
/// </summary>
/// <param name="caption">The caption of the message box.</param>
/// <param name="reportProgressAsync">
/// 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.
/// </param>
/// <param name="forceClosed">When user closes the progress box manually by button or esc key, this action will be called.</param>
/// <returns>A progress box interface.</returns>
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action forceClosed = null);
}
}
106 changes: 106 additions & 0 deletions Flow.Launcher/ProgressBoxEx.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<Window
x:Class="Flow.Launcher.ProgressBoxEx"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="MessageBoxWindow"
Width="420"
Height="Auto"
Background="{DynamicResource PopuBGColor}"
Foreground="{DynamicResource PopupTextColor}"
ResizeMode="NoResize"
SizeToContent="Height"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
</WindowChrome.WindowChrome>
<Window.InputBindings>
<KeyBinding Key="Escape" Command="Close" />
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="Close" Executed="KeyEsc_OnPress" />
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition MinHeight="68" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="1"
Click="Button_Cancel"
Style="{StaticResource TitleBarCloseButtonStyle}">
<Path
Width="46"
Height="32"
Data="M 18,11 27,20 M 18,20 27,11"
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
StrokeThickness="1">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
</Grid>
</StackPanel>
</StackPanel>
<Grid Grid.Row="1" Margin="30 0 30 24">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
x:Name="TitleTextBlock"
Grid.Row="0"
MaxWidth="400"
Margin="0 0 26 12"
VerticalAlignment="Center"
FontFamily="Segoe UI"
FontSize="20"
FontWeight="SemiBold"
TextAlignment="Left"
TextWrapping="Wrap" />
<ProgressBar
x:Name="ProgressBar"
Grid.Row="1"
Margin="0 0 26 0"
Maximum="100"
Minimum="0"
Value="0" />
</Grid>
<Border
Grid.Row="2"
Margin="0 0 0 0"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
BorderThickness="0 1 0 0">
<WrapPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button
x:Name="btnCancel"
MinWidth="120"
Margin="5 0 5 0"
Click="Button_Click"
Content="{DynamicResource commonCancel}" />
</WrapPanel>
</Border>
</Grid>
</Window>
114 changes: 114 additions & 0 deletions Flow.Launcher/ProgressBoxEx.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Flow.Launcher.Infrastructure.Logger;

namespace Flow.Launcher
{
public partial class ProgressBoxEx : Window
{
private readonly Action _forceClosed;

private ProgressBoxEx(Action forceClosed)
{
_forceClosed = forceClosed;
InitializeComponent();
}

public static async Task ShowAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action forceClosed = null)
{
ProgressBoxEx prgBox = null;
try
{
if (!Application.Current.Dispatcher.CheckAccess())
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
prgBox = new ProgressBoxEx(forceClosed)
{
Title = caption
};
prgBox.TitleTextBlock.Text = caption;
prgBox.Show();
});
}
else
{
prgBox = new ProgressBoxEx(forceClosed)
{
Title = caption
};
prgBox.TitleTextBlock.Text = caption;
prgBox.Show();
}

await reportProgressAsync(prgBox.ReportProgress).ConfigureAwait(false);
}
Jack251970 marked this conversation as resolved.
Show resolved Hide resolved
catch (Exception e)
{
Log.Error($"|ProgressBoxEx.Show|An error occurred: {e.Message}");

await reportProgressAsync(null).ConfigureAwait(false);
}
Jack251970 marked this conversation as resolved.
Show resolved Hide resolved
finally
{
if (!Application.Current.Dispatcher.CheckAccess())
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
prgBox?.Close();
});
}
else
{
prgBox?.Close();
}
}
}

private void ReportProgress(double progress)
{
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(() => ReportProgress(progress));
return;
}

if (progress < 0)
{
ProgressBar.Value = 0;
}
else if (progress >= 100)
{
ProgressBar.Value = 100;
Close();
}
else
{
ProgressBar.Value = progress;
}
}

private void KeyEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
{
ForceClose();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
ForceClose();
}

private void Button_Cancel(object sender, RoutedEventArgs e)
{
ForceClose();
}

private void ForceClose()
{
Close();
_forceClosed?.Invoke();
}
}
}
6 changes: 4 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 Expand Up @@ -324,6 +324,8 @@ public bool IsGameModeOn()
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK) =>
MessageBoxEx.Show(messageBoxText, caption, button, icon, defaultResult);

public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action forceClosed = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, forceClosed);

#endregion

#region Private Methods
Expand Down
2 changes: 1 addition & 1 deletion Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
return query.FirstSearch.ToLower() switch
{
//search could be url, no need ToLower() when passed in
Settings.InstallCommand => await pluginManager.RequestInstallOrUpdate(query.SecondToEndSearch, token, query.IsReQuery),
Settings.InstallCommand => await pluginManager.RequestInstallOrUpdateAsync(query.SecondToEndSearch, token, query.IsReQuery),
Settings.UninstallCommand => pluginManager.RequestUninstall(query.SecondToEndSearch),
Settings.UpdateCommand => await pluginManager.RequestUpdateAsync(query.SecondToEndSearch, token, query.IsReQuery),
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
Expand Down
Loading
Loading