Skip to content
107 changes: 107 additions & 0 deletions Flow.Launcher.Core/Plugin/PluginInstaller.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
Expand Down Expand Up @@ -277,6 +278,100 @@ await DownloadFileAsync(
}
}

/// <summary>
/// Updates the plugin to the latest version available from its source.
/// </summary>
/// <param name="silentUpdate">If true, do not show any messages when there is no udpate 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)
{
// Update the plugin manifest
await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);

// Get all plugins that can be updated
var resultsForUpdate = (
from existingPlugin in API.GetAllPlugins()
join pluginUpdateSource in API.GetPluginManifest()
on existingPlugin.Metadata.ID equals pluginUpdateSource.ID
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
StringComparison.InvariantCulture) <
0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
&& !API.PluginModified(existingPlugin.Metadata.ID)
select
new PluginUpdateInfo()
{
ID = existingPlugin.Metadata.ID,
Name = existingPlugin.Metadata.Name,
Author = existingPlugin.Metadata.Author,
CurrentVersion = existingPlugin.Metadata.Version,
NewVersion = pluginUpdateSource.Version,
IcoPath = existingPlugin.Metadata.IcoPath,
PluginExistingMetadata = existingPlugin.Metadata,
PluginNewUserPlugin = pluginUpdateSource
}).ToList();

// No updates
if (!resultsForUpdate.Any())
{
if (!silentUpdate)
{
API.ShowMsg(API.GetTranslation("updateNoResultTitle"), API.GetTranslation("updateNoResultSubtitle"));
}
return;
}

// If all plugins are modified, just return
if (resultsForUpdate.All(x => API.PluginModified(x.ID)))
{
return;
}

// Show message box with button to update all plugins
API.ShowMsgWithButton(
API.GetTranslation("updateAllPluginsTitle"),
API.GetTranslation("updateAllPluginsButtonContent"),
() =>
{
UpdateAllPlugins(resultsForUpdate);
},
string.Join(", ", resultsForUpdate.Select(x => x.PluginExistingMetadata.Name)));
}

private static void UpdateAllPlugins(IEnumerable<PluginUpdateInfo> resultsForUpdate)
{
_ = Task.WhenAll(resultsForUpdate.Select(async plugin =>
{
var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");

try
{
using var cts = new CancellationTokenSource();

await DownloadFileAsync(
$"{API.GetTranslation("DownloadingPlugin")} {plugin.PluginNewUserPlugin.Name}",
plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts);

// check if user cancelled download before installing plugin
if (cts.IsCancellationRequested)
{
return;
}

if (!await API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath))
{
return;
}
}
catch (Exception e)
{
API.LogException(ClassName, "Failed to update plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin"));
}
}));
}

/// <summary>
/// Downloads a file from a URL to a local path, optionally showing a progress box and handling cancellation.
/// </summary>
Expand Down Expand Up @@ -350,4 +445,16 @@ private static bool InstallSourceKnown(string url)
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; }
}
}
1 change: 1 addition & 0 deletions Flow.Launcher.Infrastructure/UserSettings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@

public bool AutoRestartAfterChanging { get; set; } = false;
public bool ShowUnknownSourceWarning { get; set; } = true;
public bool AutoUpdatePlugins { get; set; } = true;

public int CustomExplorerIndex { get; set; } = 0;

Expand Down Expand Up @@ -636,14 +637,14 @@

public enum DoublePinyinSchemas
{
XiaoHe,

Check warning on line 640 in Flow.Launcher.Infrastructure/UserSettings/Settings.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Xiao` is not a recognized word. (unrecognized-spelling)
ZiRanMa,
WeiRuan,
ZhiNengABC,
ZiGuangPinYin,
PinYinJiaJia,

Check warning on line 645 in Flow.Launcher.Infrastructure/UserSettings/Settings.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Jia` is not a recognized word. (unrecognized-spelling)
XingKongJianDao,
DaNiu,
XiaoLang

Check warning on line 648 in Flow.Launcher.Infrastructure/UserSettings/Settings.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Xiao` is not a recognized word. (unrecognized-spelling)
}
}
22 changes: 20 additions & 2 deletions Flow.Launcher/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 All @@ -239,6 +239,7 @@

AutoStartup();
AutoUpdates();
AutoPluginUpdates();

API.SaveAppAllSettings();
API.LogInfo(ClassName, "End Flow Launcher startup ----------------------------------------------------");
Expand All @@ -251,7 +252,7 @@
/// Check startup only for Release
/// </summary>
[Conditional("RELEASE")]
private void AutoStartup()
private static void AutoStartup()
{
// we try to enable auto-startup on first launch, or reenable if it was removed
// but the user still has the setting set
Expand All @@ -272,7 +273,7 @@
}

[Conditional("RELEASE")]
private void AutoUpdates()
private static void AutoUpdates()
{
_ = Task.Run(async () =>
{
Expand All @@ -289,6 +290,23 @@
});
}

private static void AutoPluginUpdates()
{
_ = Task.Run(async () =>
{
if (_settings.AutoUpdatePlugins)
{
// check plugin updates every 5 hour
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
await PluginInstaller.CheckForPluginUpdatesAsync();

while (await timer.WaitForNextTickAsync())
// check updates on startup
await PluginInstaller.CheckForPluginUpdatesAsync();
}
});
}

#endregion

#region Register Events
Expand Down
10 changes: 9 additions & 1 deletion Flow.Launcher/Languages/en.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
<system:String x:Key="typingStartEn">Always Start Typing in English Mode</system:String>
<system:String x:Key="typingStartEnTooltip">Temporarily change your input method to English mode when activating Flow.</system:String>
<system:String x:Key="autoUpdates">Auto Update</system:String>
<system:String x:Key="autoUpdatesTooltip">Automatically check app updates and notify if there are any updates available</system:String>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This auto update for the app actually updates so the description needs a bit more specific.

<system:String x:Key="select">Select</system:String>
<system:String x:Key="hideOnStartup">Hide Flow Launcher on startup</system:String>
<system:String x:Key="hideOnStartupToolTip">Flow Launcher search window is hidden in the tray after starting up.</system:String>
Expand All @@ -108,16 +109,16 @@
<system:String x:Key="ShouldUseDoublePinyin">Use Double Pinyin</system:String>
<system:String x:Key="ShouldUseDoublePinyinToolTip">Allows using Double Pinyin to search. Double Pinyin is a variation of Pinyin that uses two characters.</system:String>
<system:String x:Key="DoublePinyinSchema">Double Pinyin Schema</system:String>
<system:String x:Key="DoublePinyinSchemasXiaoHe">Xiao He</system:String>

Check warning on line 112 in Flow.Launcher/Languages/en.xaml

View workflow job for this annotation

GitHub Actions / Check Spelling

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

Check warning on line 112 in Flow.Launcher/Languages/en.xaml

View workflow job for this annotation

GitHub Actions / Check Spelling

`Xiao` is not a recognized word. (unrecognized-spelling)
<system:String x:Key="DoublePinyinSchemasZiRanMa">Zi Ran Ma</system:String>
<system:String x:Key="DoublePinyinSchemasWeiRuan">Wei Ruan</system:String>

Check warning on line 114 in Flow.Launcher/Languages/en.xaml

View workflow job for this annotation

GitHub Actions / Check Spelling

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

Check warning on line 114 in Flow.Launcher/Languages/en.xaml

View workflow job for this annotation

GitHub Actions / Check Spelling

`Ruan` is not a recognized word. (unrecognized-spelling)
<system:String x:Key="DoublePinyinSchemasZhiNengABC">Zhi Neng ABC</system:String>

Check warning on line 115 in Flow.Launcher/Languages/en.xaml

View workflow job for this annotation

GitHub Actions / Check Spelling

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

Check warning on line 115 in Flow.Launcher/Languages/en.xaml

View workflow job for this annotation

GitHub Actions / Check Spelling

`Neng` is not a recognized word. (unrecognized-spelling)
<system:String x:Key="DoublePinyinSchemasZiGuangPinYin">Zi Guang Pin Yin</system:String>
<system:String x:Key="DoublePinyinSchemasPinYinJiaJia">Pin Yin Jia Jia</system:String>
<system:String x:Key="DoublePinyinSchemasXingKongJianDao">Xing Kong Jian Dao</system:String>
<system:String x:Key="DoublePinyinSchemasDaNiu">Da Niu</system:String>
<system:String x:Key="DoublePinyinSchemasXiaoLang">Xiao Lang</system:String>

<system:String x:Key="AlwaysPreview">Always Preview</system:String>
<system:String x:Key="AlwaysPreviewToolTip">Always open preview panel when Flow activates. Press {0} to toggle preview.</system:String>
<system:String x:Key="shadowEffectNotAllowed">Shadow effect is not allowed while current theme has blur effect enabled</system:String>
Expand Down Expand Up @@ -150,6 +151,8 @@
<system:String x:Key="autoRestartAfterChangingToolTip">Restart Flow Launcher automatically after installing/uninstalling/updating plugin via Plugin Store</system:String>
<system:String x:Key="showUnknownSourceWarning">Show unknown source warning</system:String>
<system:String x:Key="showUnknownSourceWarningToolTip">Show warning when installing plugins from unknown sources</system:String>
<system:String x:Key="autoUpdatePlugins">Auto update plugins</system:String>
<system:String x:Key="autoUpdatePluginsToolTip">Automatically check plugin updates and notify if there are any updates available</system:String>

<!-- Setting Plugin -->
<system:String x:Key="searchplugin">Search Plugin</system:String>
Expand Down Expand Up @@ -231,6 +234,11 @@
<system:String x:Key="ZipFiles">Zip files</system:String>
<system:String x:Key="SelectZipFile">Please select zip file</system:String>
<system:String x:Key="installLocalPluginTooltip">Install plugin from local path</system:String>
<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="checkPluginUpdatesTooltip">Check plugin updates</system:String>

<!-- Setting Theme -->
<system:String x:Key="theme">Theme</system:String>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ private async Task InstallPluginAsync()
await PluginInstaller.InstallPluginAndCheckRestartAsync(file);
}

[RelayCommand]
private async Task CheckPluginUpdatesAsync()
{
await PluginInstaller.CheckForPluginUpdatesAsync(silentUpdate: false);
}

private static string GetFileFromDialog(string title, string filter = "")
{
var dlg = new Microsoft.Win32.OpenFileDialog
Expand Down
30 changes: 21 additions & 9 deletions Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@
<cc:Card
Title="{DynamicResource autoUpdates}"
Margin="0 14 0 0"
Icon="&#xecc5;">
Icon="&#xecc5;"
Sub="{DynamicResource autoUpdatesTooltip}">
<ui:ToggleSwitch
IsOn="{Binding AutoUpdates}"
OffContent="{DynamicResource disable}"
Expand Down Expand Up @@ -241,12 +242,23 @@
Title="{DynamicResource showUnknownSourceWarning}"
Icon="&#xE7BA;"
Sub="{DynamicResource showUnknownSourceWarningToolTip}"
Type="Last">
Type="Middle">
<ui:ToggleSwitch
IsOn="{Binding Settings.ShowUnknownSourceWarning}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>

<cc:Card
Title="{DynamicResource autoUpdatePlugins}"
Icon="&#xecc5;"
Sub="{DynamicResource autoUpdatePluginsToolTip}"
Type="Last">
<ui:ToggleSwitch
IsOn="{Binding Settings.AutoUpdatePlugins}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
</cc:CardGroup>

<cc:ExCard
Expand Down Expand Up @@ -372,7 +384,7 @@
</cc:Card>

<cc:CardGroup Margin="0 4 0 0">
<cc:Card
<cc:Card
Title="{DynamicResource ShouldUsePinyin}"
Icon="&#xe98a;"
Sub="{DynamicResource ShouldUsePinyinToolTip}"
Expand All @@ -384,24 +396,24 @@
ToolTip="{DynamicResource ShouldUsePinyinToolTip}" />
</cc:Card>
<cc:Card
Visibility="{ext:VisibleWhen {Binding ShouldUsePinyin},
IsEqualToBool=True}"
Title="{DynamicResource ShouldUseDoublePinyin}"
Icon="&#xf085;"
Sub="{DynamicResource ShouldUseDoublePinyinToolTip}"
Type="Middle">
Type="Middle"
Visibility="{ext:VisibleWhen {Binding ShouldUsePinyin},
IsEqualToBool=True}">
<ui:ToggleSwitch
IsOn="{Binding UseDoublePinyin}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}"
ToolTip="{DynamicResource ShouldUseDoublePinyinToolTip}" />
</cc:Card>
<cc:Card
Visibility="{ext:VisibleWhen {Binding UseDoublePinyin},
IsEqualToBool=True}"
Title="{DynamicResource DoublePinyinSchema}"
Sub="{DynamicResource DoublePinyinSchemaToolTip}"
Type="Last">
Type="Last"
Visibility="{ext:VisibleWhen {Binding UseDoublePinyin},
IsEqualToBool=True}">
<ComboBox
DisplayMemberPath="Display"
ItemsSource="{Binding DoublePinyinSchemas}"
Expand Down
7 changes: 7 additions & 0 deletions Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@
ToolTip="{DynamicResource installLocalPluginTooltip}">
<ui:FontIcon FontSize="14" Glyph="&#xE8DA;" />
</Button>
<Button
Height="34"
Margin="0 0 10 0"
Command="{Binding CheckPluginUpdatesCommand}"
ToolTip="{DynamicResource checkPluginUpdatesTooltip}">
<ui:FontIcon FontSize="14" Glyph="&#xecc5;" />
</Button>
<TextBox
Name="PluginStoreFilterTextbox"
Width="150"
Expand Down
Loading