Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 37 additions & 15 deletions Flow.Launcher.Core/Plugin/PluginInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
else
{
API.ShowMsg(
API.GetTranslation("installbtn"),

Check warning on line 107 in Flow.Launcher.Core/Plugin/PluginInstaller.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`installbtn` is not a recognized word. (unrecognized-spelling)
string.Format(
API.GetTranslation(
"InstallSuccessNoRestart"),
Expand All @@ -128,7 +128,7 @@

using Stream stream = pluginJsonEntry.Open();
plugin = JsonSerializer.Deserialize<UserPlugin>(stream);
plugin.IcoPath = "Images\\zipfolder.png";

Check warning on line 131 in Flow.Launcher.Core/Plugin/PluginInstaller.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`zipfolder` is not a recognized word. (unrecognized-spelling)
plugin.LocalInstallPath = filePath;
}
catch (Exception e)
Expand Down Expand Up @@ -159,7 +159,7 @@
}

/// <summary>
/// Uninstalls a plugin and restarts the application if required by settings. Prompts user for confirmation and whether to keep plugin settings.

Check warning on line 162 in Flow.Launcher.Core/Plugin/PluginInstaller.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Uninstalls` is not a recognized word. (unrecognized-spelling)
/// </summary>
/// <param name="oldPlugin">The plugin metadata to uninstall.</param>
/// <returns>A Task representing the asynchronous uninstall operation.</returns>
Expand Down Expand Up @@ -205,7 +205,7 @@
else
{
API.ShowMsg(
API.GetTranslation("uninstallbtn"),

Check warning on line 208 in Flow.Launcher.Core/Plugin/PluginInstaller.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`uninstallbtn` is not a recognized word. (unrecognized-spelling)
string.Format(
API.GetTranslation(
"UninstallSuccessNoRestart"),
Expand Down Expand Up @@ -270,7 +270,7 @@
else
{
API.ShowMsg(
API.GetTranslation("updatebtn"),

Check warning on line 273 in Flow.Launcher.Core/Plugin/PluginInstaller.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`updatebtn` is not a recognized word. (unrecognized-spelling)
string.Format(
API.GetTranslation(
"UpdateSuccessNoRestart"),
Expand All @@ -281,11 +281,12 @@
/// <summary>
/// Updates the plugin to the latest version available from its source.
/// </summary>
/// <param name="updateAllPlugins">Action to execute when the user chooses to update all plugins.</param>
/// <param name="silentUpdate">If true, do not show any messages when there is no update available.</param>
/// <param name="usePrimaryUrlOnly">If true, only use the primary URL for updates.</param>
/// <param name="token">Cancellation token to cancel the update operation.</param>
/// <returns></returns>
public static async Task CheckForPluginUpdatesAsync(bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default)
public static async Task CheckForPluginUpdatesAsync(Action<List<PluginUpdateInfo>> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default)
{
// Update the plugin manifest
await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
Expand Down Expand Up @@ -334,14 +335,20 @@
API.GetTranslation("updateAllPluginsButtonContent"),
() =>
{
UpdateAllPlugins(resultsForUpdate);
updateAllPlugins(resultsForUpdate);
},
string.Join(", ", resultsForUpdate.Select(x => x.PluginExistingMetadata.Name)));
}

private static void UpdateAllPlugins(IEnumerable<PluginUpdateInfo> resultsForUpdate)
/// <summary>
/// Updates all plugins that have available updates.
/// </summary>
/// <param name="resultsForUpdate"></param>
/// <param name="restart"></param>
public static async Task UpdateAllPluginsAsync(IEnumerable<PluginUpdateInfo> resultsForUpdate, bool restart)
{
_ = Task.WhenAll(resultsForUpdate.Select(async plugin =>
var anyPluginSuccess = false;
await Task.WhenAll(resultsForUpdate.Select(async plugin =>
{
var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");

Expand All @@ -363,13 +370,28 @@
{
return;
}

anyPluginSuccess = true;
}
catch (Exception e)
{
API.LogException(ClassName, "Failed to update plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin"));
}
}));

if (!anyPluginSuccess) return;

if (restart)
{
API.RestartApp();
}
else
{
API.ShowMsg(
API.GetTranslation("updatebtn"),

Check warning on line 392 in Flow.Launcher.Core/Plugin/PluginInstaller.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`updatebtn` is not a recognized word. (unrecognized-spelling)
API.GetTranslation("PluginsUpdateSuccessNoRestart"));
}
}

/// <summary>
Expand Down Expand Up @@ -407,7 +429,7 @@
}, cts.Cancel);

// if exception happened while downloading and user does not cancel downloading,
// we need to redownload the plugin

Check warning on line 432 in Flow.Launcher.Core/Plugin/PluginInstaller.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`redownload` is not a recognized word. (unrecognized-spelling)
if (exceptionHappened && (!cts.IsCancellationRequested))
await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
}
Expand Down Expand Up @@ -445,16 +467,16 @@
x.Metadata.Website.StartsWith(constructedUrlPart)
);
}
}

private record PluginUpdateInfo
{
public string ID { get; init; }
public string Name { get; init; }
public string Author { get; init; }
public string CurrentVersion { get; init; }
public string NewVersion { get; init; }
public string IcoPath { get; init; }
public PluginMetadata PluginExistingMetadata { get; init; }
public UserPlugin PluginNewUserPlugin { get; init; }
}
public record PluginUpdateInfo
{
public string ID { get; init; }
public string Name { get; init; }
public string Author { get; init; }
public string CurrentVersion { get; init; }
public string NewVersion { get; init; }
public string IcoPath { get; init; }
public PluginMetadata PluginExistingMetadata { get; init; }
public UserPlugin PluginNewUserPlugin { get; init; }
}
18 changes: 16 additions & 2 deletions Flow.Launcher/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@
RegisterDispatcherUnhandledException();
RegisterTaskSchedulerUnhandledException();

var imageLoadertask = ImageLoader.InitializeAsync();

Check warning on line 203 in Flow.Launcher/App.xaml.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Loadertask` is not a recognized word. (unrecognized-spelling)

AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings);

Expand All @@ -219,7 +219,7 @@
// Change language after all plugins are initialized because we need to update plugin title based on their api
await Ioc.Default.GetRequiredService<Internationalization>().InitializeLanguageAsync();

await imageLoadertask;

Check warning on line 222 in Flow.Launcher/App.xaml.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Loadertask` is not a recognized word. (unrecognized-spelling)

_mainWindow = new MainWindow();

Expand Down Expand Up @@ -298,11 +298,25 @@
{
// check plugin updates every 5 hour
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
await PluginInstaller.CheckForPluginUpdatesAsync();
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
{
Current.Dispatcher.Invoke(() =>
{
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
pluginUpdateWindow.ShowDialog();
});
});

while (await timer.WaitForNextTickAsync())
// check updates on startup
await PluginInstaller.CheckForPluginUpdatesAsync();
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
{
Current.Dispatcher.Invoke(() =>
{
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
pluginUpdateWindow.ShowDialog();
});
});
}
});
}
Expand Down Expand Up @@ -379,7 +393,7 @@

// If we call Environment.Exit(0), the application dispose will be called before _mainWindow.Close()
// Accessing _mainWindow?.Dispatcher will cause the application stuck
// So here we need to check it and just return so that we will not acees _mainWindow?.Dispatcher

Check warning on line 396 in Flow.Launcher/App.xaml.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`acees` is not a recognized word. (unrecognized-spelling)
if (!_mainWindow.CanClose)
{
return;
Expand Down
8 changes: 7 additions & 1 deletion Flow.Launcher/Languages/en.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,9 @@
<system:String x:Key="updateNoResultTitle">No update available</system:String>
<system:String x:Key="updateNoResultSubtitle">All plugins are up to date</system:String>
<system:String x:Key="updateAllPluginsTitle">Plugin updates available</system:String>
<system:String x:Key="updateAllPluginsButtonContent">Update all plugins</system:String>
<system:String x:Key="updateAllPluginsButtonContent">Update plugins</system:String>
<system:String x:Key="checkPluginUpdatesTooltip">Check plugin updates</system:String>
<system:String x:Key="PluginsUpdateSuccessNoRestart">Plugins are successfully updated. Please restart Flow.</system:String>

<!-- Setting Theme -->
<system:String x:Key="theme">Theme</system:String>
Expand Down Expand Up @@ -563,6 +564,11 @@
<system:String x:Key="update_flowlauncher_update_files">Update files</system:String>
<system:String x:Key="update_flowlauncher_update_update_description">Update description</system:String>

<!-- Plugin Update Window -->
<system:String x:Key="restartAfterUpdating">Restart Flow Launcher after updating plugins</system:String>
<system:String x:Key="updatePluginCheckboxContent">{0}: Update from v{1} to v{2}</system:String>
<system:String x:Key="updatePluginNoSelected">No plugin selected</system:String>

<!-- Welcome Window -->
<system:String x:Key="Skip">Skip</system:String>
<system:String x:Key="Welcome_Page1_Title">Welcome to Flow Launcher</system:String>
Expand Down
111 changes: 111 additions & 0 deletions Flow.Launcher/PluginUpdateWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<Window
x:Class="Flow.Launcher.PluginUpdateWindow"
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:flowlauncher="clr-namespace:Flow.Launcher"
Title="{DynamicResource updateAllPluginsButtonContent}"
Width="530"
Background="{DynamicResource PopuBGColor}"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Foreground="{DynamicResource PopupTextColor}"
Icon="Images\app.png"
ResizeMode="NoResize"
SizeToContent="Height"
Topmost="True"
WindowStartupLocation="CenterScreen">
<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="cmdEsc_OnPress" />
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="1"
Click="BtnCancel_OnClick"
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 Margin="26 0 26 0">
<TextBlock
Margin="0 0 0 12"
FontSize="20"
FontWeight="SemiBold"
Text="{DynamicResource updateAllPluginsButtonContent}"
TextAlignment="Left" />

<ScrollViewer
MaxHeight="300"
Margin="0 5 0 5"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="UpdatePluginStackPanel" />
</ScrollViewer>

<Rectangle
Height="1"
Margin="0 5 0 5"
Fill="{DynamicResource SeparatorForeground}" />

<CheckBox
Margin="0 10 0 10"
Content="{DynamicResource restartAfterUpdating}"
IsChecked="{Binding Restart, Mode=TwoWay}" />
</StackPanel>
</StackPanel>
<Border
Grid.Row="1"
Margin="0 14 0 0"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
BorderThickness="0 1 0 0">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Button
x:Name="btnCancel"
MinWidth="140"
Margin="10 0 5 0"
Click="BtnCancel_OnClick"
Content="{DynamicResource cancel}" />
<Button
x:Name="btnUpdate"
MinWidth="140"
Margin="5 0 10 0"
Click="btnUpdate_OnClick"
Content="{DynamicResource update}"
Style="{StaticResource AccentButtonStyle}" />
</StackPanel>
</Border>
</Grid>
</Window>
85 changes: 85 additions & 0 deletions Flow.Launcher/PluginUpdateWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;

namespace Flow.Launcher
{
public partial class PluginUpdateWindow : Window
{
public List<PluginUpdateInfo> Plugins { get; set; } = new();
public bool Restart { get; set; }

private readonly Settings _settings = Ioc.Default.GetRequiredService<Settings>();

public PluginUpdateWindow(List<PluginUpdateInfo> allPlugins)
{
Restart = _settings.AutoRestartAfterChanging;
InitializeComponent();
foreach (var plugin in allPlugins)
{
var checkBox = new CheckBox
{
Content = string.Format(App.API.GetTranslation("updatePluginCheckboxContent"), plugin.Name, plugin.CurrentVersion, plugin.NewVersion),
IsChecked = true,
Margin = new Thickness(0, 5, 0, 5),
Tag = plugin,
VerticalAlignment = VerticalAlignment.Center,
};
checkBox.Checked += CheckBox_Checked;
checkBox.Unchecked += CheckBox_Unchecked;
UpdatePluginStackPanel.Children.Add(checkBox);
Plugins.Add(plugin);
}
}

private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
if (sender is not CheckBox cb) return;
if (cb.Tag is not PluginUpdateInfo plugin) return;
if (!Plugins.Contains(plugin))
{
Plugins.Add(plugin);
}
}

private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
if (sender is not CheckBox cb) return;
if (cb.Tag is not PluginUpdateInfo plugin) return;
if (Plugins.Contains(plugin))
{
Plugins.Remove(plugin);
}
}

private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}

private void btnUpdate_OnClick(object sender, RoutedEventArgs e)
{
if (Plugins.Count == 0)
{
App.API.ShowMsgBox(App.API.GetTranslation("updatePluginNoSelected"));
return;
}

_ = PluginInstaller.UpdateAllPluginsAsync(Plugins, Restart);

DialogResult = true;
Close();
}

private void cmdEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
{
DialogResult = false;
Close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
Expand Down Expand Up @@ -112,7 +113,14 @@ private async Task InstallPluginAsync()
[RelayCommand]
private async Task CheckPluginUpdatesAsync()
{
await PluginInstaller.CheckForPluginUpdatesAsync(silentUpdate: false);
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
{
Application.Current.Dispatcher.Invoke(() =>
{
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
pluginUpdateWindow.ShowDialog();
});
}, silentUpdate: false);
}

private static string GetFileFromDialog(string title, string filter = "")
Expand Down
Loading