mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #3419 from Jack251970/plugin_manager_api
New API Function from PluginManifest and PluginManager
This commit is contained in:
commit
6ebb73bb29
15 changed files with 223 additions and 109 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
using Flow.Launcher.Infrastructure.Http;
|
using Flow.Launcher.Infrastructure.Http;
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.ExternalPlugins
|
namespace Flow.Launcher.Core.ExternalPlugins
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.ExternalPlugins
|
namespace Flow.Launcher.Core.ExternalPlugins
|
||||||
{
|
{
|
||||||
|
|
@ -17,11 +18,11 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
||||||
private static readonly SemaphoreSlim manifestUpdateLock = new(1);
|
private static readonly SemaphoreSlim manifestUpdateLock = new(1);
|
||||||
|
|
||||||
private static DateTime lastFetchedAt = DateTime.MinValue;
|
private static DateTime lastFetchedAt = DateTime.MinValue;
|
||||||
private static TimeSpan fetchTimeout = TimeSpan.FromMinutes(2);
|
private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2);
|
||||||
|
|
||||||
public static List<UserPlugin> UserPlugins { get; private set; }
|
public static List<UserPlugin> UserPlugins { get; private set; }
|
||||||
|
|
||||||
public static async Task<bool> UpdateManifestAsync(CancellationToken token = default, bool usePrimaryUrlOnly = false)
|
public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -43,7 +44,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Exception($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Http request failed", e);
|
Ioc.Default.GetRequiredService<IPublicAPI>().LogException(nameof(PluginsManifest), "Http request failed", e);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.ExternalPlugins
|
|
||||||
{
|
|
||||||
public record UserPlugin
|
|
||||||
{
|
|
||||||
public string ID { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Description { get; set; }
|
|
||||||
public string Author { get; set; }
|
|
||||||
public string Version { get; set; }
|
|
||||||
public string Language { get; set; }
|
|
||||||
public string Website { get; set; }
|
|
||||||
public string UrlDownload { get; set; }
|
|
||||||
public string UrlSourceCode { get; set; }
|
|
||||||
public string LocalInstallPath { get; set; }
|
|
||||||
public string IcoPath { get; set; }
|
|
||||||
public DateTime? LatestReleaseDate { get; set; }
|
|
||||||
public DateTime? DateAdded { get; set; }
|
|
||||||
|
|
||||||
public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -454,16 +454,11 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
#region Public functions
|
#region Public functions
|
||||||
|
|
||||||
public static bool PluginModified(string uuid)
|
public static bool PluginModified(string id)
|
||||||
{
|
{
|
||||||
return _modifiedPlugins.Contains(uuid);
|
return _modifiedPlugins.Contains(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url,
|
|
||||||
/// unless it's a local path installation
|
|
||||||
/// </summary>
|
|
||||||
public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
|
public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
|
||||||
{
|
{
|
||||||
InstallPlugin(newVersion, zipFilePath, checkModified:false);
|
InstallPlugin(newVersion, zipFilePath, checkModified:false);
|
||||||
|
|
@ -471,17 +466,11 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
_modifiedPlugins.Add(existingVersion.ID);
|
_modifiedPlugins.Add(existingVersion.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Install a plugin. By default will remove the zip file if installation is from url, unless it's a local path installation
|
|
||||||
/// </summary>
|
|
||||||
public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
|
public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
|
||||||
{
|
{
|
||||||
InstallPlugin(plugin, zipFilePath, checkModified: true);
|
InstallPlugin(plugin, zipFilePath, checkModified: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Uninstall a plugin.
|
|
||||||
/// </summary>
|
|
||||||
public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
|
public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
|
||||||
{
|
{
|
||||||
await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true);
|
await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true);
|
||||||
|
|
@ -525,20 +514,20 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
|
var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
|
||||||
|
|
||||||
var defaultPluginIDs = new List<string>
|
var defaultPluginIDs = new List<string>
|
||||||
{
|
{
|
||||||
"0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
|
"0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
|
||||||
"CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
|
"CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
|
||||||
"572be03c74c642baae319fc283e561a8", // Explorer
|
"572be03c74c642baae319fc283e561a8", // Explorer
|
||||||
"6A122269676E40EB86EB543B945932B9", // PluginIndicator
|
"6A122269676E40EB86EB543B945932B9", // PluginIndicator
|
||||||
"9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
|
"9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
|
||||||
"b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
|
"b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
|
||||||
"791FC278BA414111B8D1886DFE447410", // Program
|
"791FC278BA414111B8D1886DFE447410", // Program
|
||||||
"D409510CD0D2481F853690A07E6DC426", // Shell
|
"D409510CD0D2481F853690A07E6DC426", // Shell
|
||||||
"CEA08895D2544B019B2E9C5009600DF4", // Sys
|
"CEA08895D2544B019B2E9C5009600DF4", // Sys
|
||||||
"0308FD86DE0A4DEE8D62B9B535370992", // URL
|
"0308FD86DE0A4DEE8D62B9B535370992", // URL
|
||||||
"565B73353DBF4806919830B9202EE3BF", // WebSearch
|
"565B73353DBF4806919830B9202EE3BF", // WebSearch
|
||||||
"5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
|
"5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
|
||||||
};
|
};
|
||||||
|
|
||||||
// Treat default plugin differently, it needs to be removable along with each flow release
|
// Treat default plugin differently, it needs to be removable along with each flow release
|
||||||
var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
|
var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
|
||||||
|
|
|
||||||
|
|
@ -344,5 +344,62 @@ namespace Flow.Launcher.Plugin
|
||||||
/// Stop the loading bar in main window
|
/// Stop the loading bar in main window
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StopLoadingBar();
|
public void StopLoadingBar();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the plugin manifest
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="usePrimaryUrlOnly">
|
||||||
|
/// FL has multiple urls to download the plugin manifest. Set this to true to only use the primary url.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns>True if the manifest is updated successfully, false otherwise</returns>
|
||||||
|
public Task<bool> UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the plugin manifest
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IReadOnlyList<UserPlugin> GetPluginManifest();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the plugin has been modified.
|
||||||
|
/// If this plugin is updated, installed or uninstalled and users do not restart the app,
|
||||||
|
/// it will be marked as modified
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Plugin id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool PluginModified(string id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url,
|
||||||
|
/// unless it's a local path installation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginMetadata">The metadata of the old plugin to update</param>
|
||||||
|
/// <param name="plugin">The new plugin to update</param>
|
||||||
|
/// <param name="zipFilePath">
|
||||||
|
/// Path to the zip file containing the plugin. It will be unzipped to the temporary directory, removed and installed.
|
||||||
|
/// </param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Install a plugin. By default will remove the zip file if installation is from url,
|
||||||
|
/// unless it's a local path installation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin">The plugin to install</param>
|
||||||
|
/// <param name="zipFilePath">
|
||||||
|
/// Path to the zip file containing the plugin. It will be unzipped to the temporary directory, removed and installed.
|
||||||
|
/// </param>
|
||||||
|
public void InstallPlugin(UserPlugin plugin, string zipFilePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uninstall a plugin
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginMetadata">The metadata of the plugin to uninstall</param>
|
||||||
|
/// <param name="removePluginSettings">
|
||||||
|
/// Plugin has their own settings. If this is set to true, the plugin settings will be removed.
|
||||||
|
/// </param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
80
Flow.Launcher.Plugin/UserPlugin.cs
Normal file
80
Flow.Launcher.Plugin/UserPlugin.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Flow.Launcher.Plugin
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// User Plugin Model for Flow Launcher
|
||||||
|
/// </summary>
|
||||||
|
public record UserPlugin
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unique identifier of the plugin
|
||||||
|
/// </summary>
|
||||||
|
public string ID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the plugin
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Description of the plugin
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Author of the plugin
|
||||||
|
/// </summary>
|
||||||
|
public string Author { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Version of the plugin
|
||||||
|
/// </summary>
|
||||||
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allow language of the plugin <see cref="AllowedLanguage"/>
|
||||||
|
/// </summary>
|
||||||
|
public string Language { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Website of the plugin
|
||||||
|
/// </summary>
|
||||||
|
public string Website { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL to download the plugin
|
||||||
|
/// </summary>
|
||||||
|
public string UrlDownload { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL to the source code of the plugin
|
||||||
|
/// </summary>
|
||||||
|
public string UrlSourceCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Local path where the plugin is installed
|
||||||
|
/// </summary>
|
||||||
|
public string LocalInstallPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Icon path of the plugin
|
||||||
|
/// </summary>
|
||||||
|
public string IcoPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The date when the plugin was last updated
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? LatestReleaseDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The date when the plugin was added to the local system
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? DateAdded { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the plugin is installed from a local path
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using Flow.Launcher.ViewModel;
|
using Flow.Launcher.ViewModel;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Flow.Launcher.Core.Resource;
|
using Flow.Launcher.Core.Resource;
|
||||||
|
using Flow.Launcher.Core.ExternalPlugins;
|
||||||
|
|
||||||
namespace Flow.Launcher
|
namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
|
|
@ -354,6 +355,22 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);
|
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);
|
||||||
|
|
||||||
|
public Task<bool> UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) =>
|
||||||
|
PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token);
|
||||||
|
|
||||||
|
public IReadOnlyList<UserPlugin> GetPluginManifest() => PluginsManifest.UserPlugins;
|
||||||
|
|
||||||
|
public bool PluginModified(string id) => PluginManager.PluginModified(id);
|
||||||
|
|
||||||
|
public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath) =>
|
||||||
|
PluginManager.UpdatePluginAsync(pluginMetadata, plugin, zipFilePath);
|
||||||
|
|
||||||
|
public void InstallPlugin(UserPlugin plugin, string zipFilePath) =>
|
||||||
|
PluginManager.InstallPlugin(plugin, zipFilePath);
|
||||||
|
|
||||||
|
public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false) =>
|
||||||
|
PluginManager.UninstallPluginAsync(pluginMetadata, removePluginSettings);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Private Methods
|
#region Private Methods
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Flow.Launcher.Core.ExternalPlugins;
|
|
||||||
using Flow.Launcher.Infrastructure;
|
using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using Flow.Launcher.ViewModel;
|
using Flow.Launcher.ViewModel;
|
||||||
|
|
@ -14,7 +13,7 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
|
||||||
public string FilterText { get; set; } = string.Empty;
|
public string FilterText { get; set; } = string.Empty;
|
||||||
|
|
||||||
public IList<PluginStoreItemViewModel> ExternalPlugins =>
|
public IList<PluginStoreItemViewModel> ExternalPlugins =>
|
||||||
PluginsManifest.UserPlugins?.Select(p => new PluginStoreItemViewModel(p))
|
App.API.GetPluginManifest()?.Select(p => new PluginStoreItemViewModel(p))
|
||||||
.OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
|
.OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
|
||||||
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
|
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
|
||||||
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
|
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
|
||||||
|
|
@ -24,7 +23,7 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task RefreshExternalPluginsAsync()
|
private async Task RefreshExternalPluginsAsync()
|
||||||
{
|
{
|
||||||
if (await PluginsManifest.UpdateManifestAsync())
|
if (await App.API.UpdatePluginManifestAsync())
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(ExternalPlugins));
|
OnPropertyChanged(nameof(ExternalPlugins));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Flow.Launcher.Core.ExternalPlugins;
|
|
||||||
using Flow.Launcher.Core.Plugin;
|
using Flow.Launcher.Core.Plugin;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using SemanticVersioning;
|
|
||||||
using Version = SemanticVersioning.Version;
|
using Version = SemanticVersioning.Version;
|
||||||
|
|
||||||
namespace Flow.Launcher.ViewModel
|
namespace Flow.Launcher.ViewModel
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Flow.Launcher.Core.ExternalPlugins;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.PluginsManager
|
namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
|
|
||||||
<ProjectReference Include="..\..\Flow.Launcher.Core\Flow.Launcher.Core.csproj" />
|
|
||||||
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
@ -37,4 +35,8 @@
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
using Flow.Launcher.Core.ExternalPlugins;
|
using System.Collections.Generic;
|
||||||
using Flow.Launcher.Plugin.PluginsManager.ViewModels;
|
|
||||||
using Flow.Launcher.Plugin.PluginsManager.Views;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using Flow.Launcher.Infrastructure;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Flow.Launcher.Plugin.PluginsManager.ViewModels;
|
||||||
|
using Flow.Launcher.Plugin.PluginsManager.Views;
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.PluginsManager
|
namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
{
|
{
|
||||||
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n
|
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n
|
||||||
{
|
{
|
||||||
internal PluginInitContext Context { get; set; }
|
internal static PluginInitContext Context { get; set; }
|
||||||
|
|
||||||
internal Settings Settings;
|
internal Settings Settings;
|
||||||
|
|
||||||
|
|
@ -35,7 +33,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
contextMenu = new ContextMenu(Context);
|
contextMenu = new ContextMenu(Context);
|
||||||
pluginManager = new PluginsManager(Context, Settings);
|
pluginManager = new PluginsManager(Context, Settings);
|
||||||
|
|
||||||
await PluginsManifest.UpdateManifestAsync();
|
await Context.API.UpdatePluginManifestAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Result> LoadContextMenus(Result selectedResult)
|
public List<Result> LoadContextMenus(Result selectedResult)
|
||||||
|
|
@ -56,7 +54,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
Settings.UpdateCommand => await pluginManager.RequestUpdateAsync(query.SecondToEndSearch, token, query.IsReQuery),
|
Settings.UpdateCommand => await pluginManager.RequestUpdateAsync(query.SecondToEndSearch, token, query.IsReQuery),
|
||||||
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
|
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
|
||||||
{
|
{
|
||||||
hotkey.Score = StringMatcher.FuzzySearch(query.Search, hotkey.Title).Score;
|
hotkey.Score = Context.API.FuzzySearch(query.Search, hotkey.Title).Score;
|
||||||
return hotkey.Score > 0;
|
return hotkey.Score > 0;
|
||||||
}).ToList()
|
}).ToList()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
using Flow.Launcher.Core.ExternalPlugins;
|
using System;
|
||||||
using Flow.Launcher.Core.Plugin;
|
|
||||||
using Flow.Launcher.Infrastructure;
|
|
||||||
using Flow.Launcher.Infrastructure.Http;
|
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -12,12 +6,15 @@ using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.PluginsManager
|
namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
{
|
{
|
||||||
internal class PluginsManager
|
internal class PluginsManager
|
||||||
{
|
{
|
||||||
private const string zip = "zip";
|
private const string ZipSuffix = "zip";
|
||||||
|
|
||||||
|
private static readonly string ClassName = nameof(PluginsManager);
|
||||||
|
|
||||||
private PluginInitContext Context { get; set; }
|
private PluginInitContext Context { get; set; }
|
||||||
|
|
||||||
|
|
@ -51,7 +48,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
{
|
{
|
||||||
return new List<Result>()
|
return new List<Result>()
|
||||||
{
|
{
|
||||||
new Result()
|
new()
|
||||||
{
|
{
|
||||||
Title = Settings.InstallCommand,
|
Title = Settings.InstallCommand,
|
||||||
IcoPath = icoPath,
|
IcoPath = icoPath,
|
||||||
|
|
@ -63,7 +60,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Result()
|
new()
|
||||||
{
|
{
|
||||||
Title = Settings.UninstallCommand,
|
Title = Settings.UninstallCommand,
|
||||||
IcoPath = icoPath,
|
IcoPath = icoPath,
|
||||||
|
|
@ -75,7 +72,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Result()
|
new()
|
||||||
{
|
{
|
||||||
Title = Settings.UpdateCommand,
|
Title = Settings.UpdateCommand,
|
||||||
IcoPath = icoPath,
|
IcoPath = icoPath,
|
||||||
|
|
@ -169,7 +166,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
Context.API.ShowMsgError(
|
Context.API.ShowMsgError(
|
||||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
|
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
|
||||||
Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
|
Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
|
||||||
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
|
Context.API.LogException(ClassName, "An error occurred while downloading plugin", e);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +176,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
|
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
|
||||||
plugin.Name));
|
plugin.Name));
|
||||||
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
|
Context.API.LogException(ClassName, "An error occurred while downloading plugin", e);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +234,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
internal async ValueTask<List<Result>> RequestUpdateAsync(string search, CancellationToken token,
|
internal async ValueTask<List<Result>> RequestUpdateAsync(string search, CancellationToken token,
|
||||||
bool usePrimaryUrlOnly = false)
|
bool usePrimaryUrlOnly = false)
|
||||||
{
|
{
|
||||||
await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly);
|
await Context.API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
|
||||||
|
|
||||||
var pluginFromLocalPath = null as UserPlugin;
|
var pluginFromLocalPath = null as UserPlugin;
|
||||||
var updateFromLocalPath = false;
|
var updateFromLocalPath = false;
|
||||||
|
|
@ -250,7 +247,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
}
|
}
|
||||||
|
|
||||||
var updateSource = !updateFromLocalPath
|
var updateSource = !updateFromLocalPath
|
||||||
? PluginsManifest.UserPlugins
|
? Context.API.GetPluginManifest()
|
||||||
: new List<UserPlugin> { pluginFromLocalPath };
|
: new List<UserPlugin> { pluginFromLocalPath };
|
||||||
|
|
||||||
var resultsForUpdate = (
|
var resultsForUpdate = (
|
||||||
|
|
@ -260,7 +257,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
|
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
|
||||||
StringComparison.InvariantCulture) <
|
StringComparison.InvariantCulture) <
|
||||||
0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
|
0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
|
||||||
&& !PluginManager.PluginModified(existingPlugin.Metadata.ID)
|
&& !Context.API.PluginModified(existingPlugin.Metadata.ID)
|
||||||
select
|
select
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
|
|
@ -276,7 +273,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
if (!resultsForUpdate.Any())
|
if (!resultsForUpdate.Any())
|
||||||
return new List<Result>
|
return new List<Result>
|
||||||
{
|
{
|
||||||
new Result
|
new()
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"),
|
Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"),
|
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"),
|
||||||
|
|
@ -341,7 +338,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await PluginManager.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
|
await Context.API.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
|
||||||
downloadToFilePath);
|
downloadToFilePath);
|
||||||
|
|
||||||
if (Settings.AutoRestartAfterChanging)
|
if (Settings.AutoRestartAfterChanging)
|
||||||
|
|
@ -366,7 +363,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
}
|
}
|
||||||
}).ContinueWith(t =>
|
}).ContinueWith(t =>
|
||||||
{
|
{
|
||||||
Log.Exception("PluginsManager", $"Update failed for {x.Name}",
|
Context.API.LogException(ClassName, $"Update failed for {x.Name}",
|
||||||
t.Exception.InnerException);
|
t.Exception.InnerException);
|
||||||
Context.API.ShowMsg(
|
Context.API.ShowMsg(
|
||||||
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||||
|
|
@ -433,12 +430,12 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
if (cts.IsCancellationRequested)
|
if (cts.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
else
|
else
|
||||||
await PluginManager.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
|
await Context.API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
|
||||||
downloadToFilePath);
|
downloadToFilePath);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Exception("PluginsManager", $"Update failed for {plugin.Name}", ex.InnerException);
|
Context.API.LogException(ClassName, $"Update failed for {plugin.Name}", ex.InnerException);
|
||||||
Context.API.ShowMsg(
|
Context.API.ShowMsg(
|
||||||
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||||
string.Format(
|
string.Format(
|
||||||
|
|
@ -486,7 +483,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
return results
|
return results
|
||||||
.Where(x =>
|
.Where(x =>
|
||||||
{
|
{
|
||||||
var matchResult = StringMatcher.FuzzySearch(searchName, x.Title);
|
var matchResult = Context.API.FuzzySearch(searchName, x.Title);
|
||||||
if (matchResult.IsSearchPrecisionScoreMet())
|
if (matchResult.IsSearchPrecisionScoreMet())
|
||||||
x.Score = matchResult.Score;
|
x.Score = matchResult.Score;
|
||||||
|
|
||||||
|
|
@ -498,7 +495,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
internal List<Result> InstallFromWeb(string url)
|
internal List<Result> InstallFromWeb(string url)
|
||||||
{
|
{
|
||||||
var filename = url.Split("/").Last();
|
var filename = url.Split("/").Last();
|
||||||
var name = filename.Split(string.Format(".{0}", zip)).First();
|
var name = filename.Split(string.Format(".{0}", ZipSuffix)).First();
|
||||||
|
|
||||||
var plugin = new UserPlugin
|
var plugin = new UserPlugin
|
||||||
{
|
{
|
||||||
|
|
@ -552,7 +549,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
|
|
||||||
return new List<Result>
|
return new List<Result>
|
||||||
{
|
{
|
||||||
new Result
|
new()
|
||||||
{
|
{
|
||||||
Title = $"{plugin.Name} by {plugin.Author}",
|
Title = $"{plugin.Name} by {plugin.Author}",
|
||||||
SubTitle = plugin.Description,
|
SubTitle = plugin.Description,
|
||||||
|
|
@ -602,19 +599,18 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
internal async ValueTask<List<Result>> RequestInstallOrUpdateAsync(string search, CancellationToken token,
|
internal async ValueTask<List<Result>> RequestInstallOrUpdateAsync(string search, CancellationToken token,
|
||||||
bool usePrimaryUrlOnly = false)
|
bool usePrimaryUrlOnly = false)
|
||||||
{
|
{
|
||||||
await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly);
|
await Context.API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
|
||||||
|
|
||||||
if (Uri.IsWellFormedUriString(search, UriKind.Absolute)
|
if (Uri.IsWellFormedUriString(search, UriKind.Absolute)
|
||||||
&& search.Split('.').Last() == zip)
|
&& search.Split('.').Last() == ZipSuffix)
|
||||||
return InstallFromWeb(search);
|
return InstallFromWeb(search);
|
||||||
|
|
||||||
if (FilesFolders.IsZipFilePath(search, checkFileExists: true))
|
if (FilesFolders.IsZipFilePath(search, checkFileExists: true))
|
||||||
return InstallFromLocalPath(search);
|
return InstallFromLocalPath(search);
|
||||||
|
|
||||||
var results =
|
var results =
|
||||||
PluginsManifest
|
Context.API.GetPluginManifest()
|
||||||
.UserPlugins
|
.Where(x => !PluginExists(x.ID) && !Context.API.PluginModified(x.ID))
|
||||||
.Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID))
|
|
||||||
.Select(x =>
|
.Select(x =>
|
||||||
new Result
|
new Result
|
||||||
{
|
{
|
||||||
|
|
@ -647,7 +643,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PluginManager.InstallPlugin(plugin, downloadedFilePath);
|
Context.API.InstallPlugin(plugin, downloadedFilePath);
|
||||||
|
|
||||||
if (!plugin.IsFromLocalInstallPath)
|
if (!plugin.IsFromLocalInstallPath)
|
||||||
File.Delete(downloadedFilePath);
|
File.Delete(downloadedFilePath);
|
||||||
|
|
@ -656,21 +652,21 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
{
|
{
|
||||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||||
Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
|
Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
|
||||||
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
|
Context.API.LogException(ClassName, e.Message, e);
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException e)
|
catch (InvalidOperationException e)
|
||||||
{
|
{
|
||||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"),
|
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"),
|
||||||
plugin.Name));
|
plugin.Name));
|
||||||
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
|
Context.API.LogException(ClassName, e.Message, e);
|
||||||
}
|
}
|
||||||
catch (ArgumentException e)
|
catch (ArgumentException e)
|
||||||
{
|
{
|
||||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"),
|
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"),
|
||||||
plugin.Name));
|
plugin.Name));
|
||||||
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
|
Context.API.LogException(ClassName, e.Message, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -740,11 +736,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
||||||
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"),
|
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"),
|
||||||
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"),
|
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"),
|
||||||
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
|
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
|
||||||
await PluginManager.UninstallPluginAsync(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings);
|
await Context.API.UninstallPluginAsync(plugin, removePluginSettings);
|
||||||
}
|
}
|
||||||
catch (ArgumentException e)
|
catch (ArgumentException e)
|
||||||
{
|
{
|
||||||
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
|
Context.API.LogException(ClassName, e.Message, e);
|
||||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"),
|
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"),
|
||||||
Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"));
|
Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Flow.Launcher.Core.ExternalPlugins;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue