diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
index 33963c01a5b..a79f4b47ce8 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
@@ -277,6 +278,100 @@ await DownloadFileAsync(
}
}
+ ///
+ /// Updates the plugin to the latest version available from its source.
+ ///
+ /// If true, do not show any messages when there is no update available.
+ /// If true, only use the primary URL for updates.
+ /// Cancellation token to cancel the update operation.
+ ///
+ 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 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"));
+ }
+ }));
+ }
+
///
/// Downloads a file from a URL to a local path, optionally showing a progress box and handling cancellation.
///
@@ -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; }
+ }
}
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index 271f618da9a..00ecb9bb4fd 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -233,6 +233,7 @@ public bool ShowHistoryResultsForHomePage
public bool AutoRestartAfterChanging { get; set; } = false;
public bool ShowUnknownSourceWarning { get; set; } = true;
+ public bool AutoUpdatePlugins { get; set; } = true;
public int CustomExplorerIndex { get; set; } = 0;
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 7b82748fca3..7e3915b2b5d 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -239,6 +239,7 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
AutoStartup();
AutoUpdates();
+ AutoPluginUpdates();
API.SaveAppAllSettings();
API.LogInfo(ClassName, "End Flow Launcher startup ----------------------------------------------------");
@@ -251,7 +252,7 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
/// Check startup only for Release
///
[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
@@ -272,7 +273,7 @@ private void AutoStartup()
}
[Conditional("RELEASE")]
- private void AutoUpdates()
+ private static void AutoUpdates()
{
_ = Task.Run(async () =>
{
@@ -289,6 +290,23 @@ private void AutoUpdates()
});
}
+ 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
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 2fca066059a..725d8d3e1b3 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -93,6 +93,7 @@
Always Start Typing in English Mode
Temporarily change your input method to English mode when activating Flow.
Auto Update
+ Automatically check and update the app when available
Select
Hide Flow Launcher on startup
Flow Launcher search window is hidden in the tray after starting up.
@@ -117,7 +118,7 @@
Xing Kong Jian Dao
Da Niu
Xiao Lang
-
+
Always Preview
Always open preview panel when Flow activates. Press {0} to toggle preview.
Shadow effect is not allowed while current theme has blur effect enabled
@@ -150,6 +151,8 @@
Restart Flow Launcher automatically after installing/uninstalling/updating plugin via Plugin Store
Show unknown source warning
Show warning when installing plugins from unknown sources
+ Auto update plugins
+ Automatically check plugin updates and notify if there are any updates available
Search Plugin
@@ -231,6 +234,11 @@
Zip files
Please select zip file
Install plugin from local path
+ No update available
+ All plugins are up to date
+ Plugin updates available
+ Update all plugins
+ Check plugin updates
Theme
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
index efe67d01664..96cd4407233 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
@@ -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
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
index a879007c349..f539510b0e0 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
@@ -182,7 +182,8 @@
+ Icon=""
+ Sub="{DynamicResource autoUpdatesTooltip}">
+ Type="Middle">
+
+
+
+
+