mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #972 from Flow-Launcher/refactor_plugin_manifest
Improvements to PluginManager plugin
This commit is contained in:
commit
b9048f50f0
7 changed files with 71 additions and 149 deletions
|
|
@ -2,6 +2,8 @@
|
|||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -10,43 +12,43 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
|||
{
|
||||
public static class PluginsManifest
|
||||
{
|
||||
static PluginsManifest()
|
||||
{
|
||||
UpdateTask = UpdateManifestAsync();
|
||||
}
|
||||
|
||||
public static List<UserPlugin> UserPlugins { get; private set; } = new List<UserPlugin>();
|
||||
|
||||
public static Task UpdateTask { get; private set; }
|
||||
private const string manifestFileUrl = "https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json";
|
||||
|
||||
private static readonly SemaphoreSlim manifestUpdateLock = new(1);
|
||||
|
||||
public static Task UpdateManifestAsync()
|
||||
{
|
||||
if (manifestUpdateLock.CurrentCount == 0)
|
||||
{
|
||||
return UpdateTask;
|
||||
}
|
||||
private static string latestEtag = "";
|
||||
|
||||
return UpdateTask = DownloadManifestAsync();
|
||||
}
|
||||
public static List<UserPlugin> UserPlugins { get; private set; } = new List<UserPlugin>();
|
||||
|
||||
private static async Task DownloadManifestAsync()
|
||||
public static async Task UpdateManifestAsync(CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await manifestUpdateLock.WaitAsync().ConfigureAwait(false);
|
||||
await manifestUpdateLock.WaitAsync(token).ConfigureAwait(false);
|
||||
|
||||
await using var jsonStream = await Http.GetStreamAsync("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/plugin_api_v2/plugins.json")
|
||||
.ConfigureAwait(false);
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, manifestFileUrl);
|
||||
request.Headers.Add("If-None-Match", latestEtag);
|
||||
|
||||
UserPlugins = await JsonSerializer.DeserializeAsync<List<UserPlugin>>(jsonStream).ConfigureAwait(false);
|
||||
var response = await Http.SendAsync(request, token).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
Log.Info($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Fetched plugins from manifest repo");
|
||||
|
||||
var json = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false);
|
||||
|
||||
UserPlugins = await JsonSerializer.DeserializeAsync<List<UserPlugin>>(json, cancellationToken: token).ConfigureAwait(false);
|
||||
|
||||
latestEtag = response.Headers.ETag.Tag;
|
||||
}
|
||||
else if (response.StatusCode != HttpStatusCode.NotModified)
|
||||
{
|
||||
Log.Warn($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Http response for manifest file was {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception("|PluginManagement.GetManifest|Encountered error trying to download plugins manifest", e);
|
||||
|
||||
UserPlugins = new List<UserPlugin>();
|
||||
Log.Exception($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Http request failed", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -153,5 +153,13 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
|
||||
return await response.Content.ReadAsStreamAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchrously send an HTTP request.
|
||||
/// </summary>
|
||||
public static async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token = default)
|
||||
{
|
||||
return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Flow.Launcher.Plugin.PluginsManager
|
||||
{
|
||||
|
|
@ -53,8 +53,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
{
|
||||
// standard UrlSourceCode format in PluginsManifest's plugins.json file: https://github.com/jjw24/Flow.Launcher.Plugin.Putty/tree/master
|
||||
var link = pluginManifestInfo.UrlSourceCode.StartsWith("https://github.com")
|
||||
? pluginManifestInfo.UrlSourceCode.Replace("/tree/master", "/issues/new/choose")
|
||||
: pluginManifestInfo.UrlSourceCode;
|
||||
? Regex.Replace(pluginManifestInfo.UrlSourceCode, @"\/tree\/\w+$", "") + "/issues/new/choose"
|
||||
: pluginManifestInfo.UrlSourceCode;
|
||||
|
||||
Context.API.OpenUrl(link);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using System.Windows;
|
|||
|
||||
namespace Flow.Launcher.Plugin.PluginsManager
|
||||
{
|
||||
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n, IAsyncReloadable
|
||||
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n
|
||||
{
|
||||
internal PluginInitContext Context { get; set; }
|
||||
|
||||
|
|
@ -24,28 +24,20 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
|
||||
internal PluginsManager pluginManager;
|
||||
|
||||
private DateTime lastUpdateTime = DateTime.MinValue;
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
return new PluginsManagerSettings(viewModel);
|
||||
}
|
||||
|
||||
public Task InitAsync(PluginInitContext context)
|
||||
public async Task InitAsync(PluginInitContext context)
|
||||
{
|
||||
Context = context;
|
||||
Settings = context.API.LoadSettingJsonStorage<Settings>();
|
||||
viewModel = new SettingsViewModel(context, Settings);
|
||||
contextMenu = new ContextMenu(Context);
|
||||
pluginManager = new PluginsManager(Context, Settings);
|
||||
_manifestUpdateTask = pluginManager
|
||||
.UpdateManifestAsync(true)
|
||||
.ContinueWith(_ =>
|
||||
{
|
||||
lastUpdateTime = DateTime.Now;
|
||||
}, TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
|
||||
return Task.CompletedTask;
|
||||
await pluginManager.UpdateManifestAsync();
|
||||
}
|
||||
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
|
|
@ -53,35 +45,20 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
return contextMenu.LoadContextMenus(selectedResult);
|
||||
}
|
||||
|
||||
private Task _manifestUpdateTask = Task.CompletedTask;
|
||||
|
||||
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||
{
|
||||
var search = query.Search;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(search))
|
||||
if (string.IsNullOrWhiteSpace(query.Search))
|
||||
return pluginManager.GetDefaultHotKeys();
|
||||
|
||||
if ((DateTime.Now - lastUpdateTime).TotalHours > 12 && _manifestUpdateTask.IsCompleted) // 12 hours
|
||||
{
|
||||
_manifestUpdateTask = pluginManager.UpdateManifestAsync().ContinueWith(t =>
|
||||
{
|
||||
lastUpdateTime = DateTime.Now;
|
||||
}, TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
}
|
||||
|
||||
return search switch
|
||||
return query.FirstSearch.ToLower() switch
|
||||
{
|
||||
//search could be url, no need ToLower() when passed in
|
||||
var s when s.StartsWith(Settings.HotKeyInstall, StringComparison.OrdinalIgnoreCase)
|
||||
=> await pluginManager.RequestInstallOrUpdate(search, token),
|
||||
var s when s.StartsWith(Settings.HotkeyUninstall, StringComparison.OrdinalIgnoreCase)
|
||||
=> pluginManager.RequestUninstall(search),
|
||||
var s when s.StartsWith(Settings.HotkeyUpdate, StringComparison.OrdinalIgnoreCase)
|
||||
=> await pluginManager.RequestUpdate(search, token),
|
||||
Settings.InstallCommand => await pluginManager.RequestInstallOrUpdate(query.SecondToEndSearch, token),
|
||||
Settings.UninstallCommand => pluginManager.RequestUninstall(query.SecondToEndSearch),
|
||||
Settings.UpdateCommand => await pluginManager.RequestUpdate(query.SecondToEndSearch, token),
|
||||
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
|
||||
{
|
||||
hotkey.Score = StringMatcher.FuzzySearch(search, hotkey.Title).Score;
|
||||
hotkey.Score = StringMatcher.FuzzySearch(query.Search, hotkey.Title).Score;
|
||||
return hotkey.Score > 0;
|
||||
}).ToList()
|
||||
};
|
||||
|
|
@ -96,11 +73,5 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
{
|
||||
return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description");
|
||||
}
|
||||
|
||||
public async Task ReloadDataAsync()
|
||||
{
|
||||
await pluginManager.UpdateManifestAsync();
|
||||
lastUpdateTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
|
||||
private Task _downloadManifestTask = Task.CompletedTask;
|
||||
|
||||
internal Task UpdateManifestAsync(bool silent = false)
|
||||
internal Task UpdateManifestAsync(CancellationToken token = default, bool silent = false)
|
||||
{
|
||||
if (_downloadManifestTask.Status == TaskStatus.Running)
|
||||
{
|
||||
|
|
@ -59,7 +59,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
}
|
||||
else
|
||||
{
|
||||
_downloadManifestTask = PluginsManifest.UpdateTask;
|
||||
_downloadManifestTask = PluginsManifest.UpdateManifestAsync(token);
|
||||
if (!silent)
|
||||
_downloadManifestTask.ContinueWith(_ =>
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_failed_title"),
|
||||
|
|
@ -75,31 +75,34 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
{
|
||||
new Result()
|
||||
{
|
||||
Title = Settings.HotKeyInstall,
|
||||
Title = Settings.InstallCommand,
|
||||
IcoPath = icoPath,
|
||||
AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.InstallCommand} ",
|
||||
Action = _ =>
|
||||
{
|
||||
Context.API.ChangeQuery("pm install ");
|
||||
Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.InstallCommand} ");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
new Result()
|
||||
{
|
||||
Title = Settings.HotkeyUninstall,
|
||||
Title = Settings.UninstallCommand,
|
||||
IcoPath = icoPath,
|
||||
AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UninstallCommand} ",
|
||||
Action = _ =>
|
||||
{
|
||||
Context.API.ChangeQuery("pm uninstall ");
|
||||
Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UninstallCommand} ");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
new Result()
|
||||
{
|
||||
Title = Settings.HotkeyUpdate,
|
||||
Title = Settings.UpdateCommand,
|
||||
IcoPath = icoPath,
|
||||
AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UpdateCommand} ",
|
||||
Action = _ =>
|
||||
{
|
||||
Context.API.ChangeQuery("pm update ");
|
||||
Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UpdateCommand} ");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -119,7 +122,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
Context
|
||||
.API
|
||||
.ChangeQuery(
|
||||
$"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.HotkeyUpdate} {plugin.Name}");
|
||||
$"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {plugin.Name}");
|
||||
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
mainWindow.Show();
|
||||
|
|
@ -181,22 +184,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
|
||||
internal async ValueTask<List<Result>> RequestUpdate(string search, CancellationToken token)
|
||||
{
|
||||
if (!PluginsManifest.UserPlugins.Any())
|
||||
{
|
||||
await UpdateManifestAsync();
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var autocompletedResults = AutoCompleteReturnAllResults(search,
|
||||
Settings.HotkeyUpdate,
|
||||
"Update",
|
||||
"Select a plugin to update");
|
||||
|
||||
if (autocompletedResults.Any())
|
||||
return autocompletedResults;
|
||||
|
||||
var uninstallSearch = search.Replace(Settings.HotkeyUpdate, string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart();
|
||||
await UpdateManifestAsync(token);
|
||||
|
||||
var resultsForUpdate =
|
||||
from existingPlugin in Context.API.GetAllPlugins()
|
||||
|
|
@ -285,7 +273,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
}
|
||||
});
|
||||
|
||||
return Search(results, uninstallSearch);
|
||||
return Search(results, search);
|
||||
}
|
||||
|
||||
internal bool PluginExists(string id)
|
||||
|
|
@ -369,20 +357,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
return url.StartsWith(acceptedSource) && Context.API.GetAllPlugins().Any(x => x.Metadata.Website.StartsWith(contructedUrlPart));
|
||||
}
|
||||
|
||||
internal async ValueTask<List<Result>> RequestInstallOrUpdate(string searchName, CancellationToken token)
|
||||
internal async ValueTask<List<Result>> RequestInstallOrUpdate(string search, CancellationToken token)
|
||||
{
|
||||
if (!PluginsManifest.UserPlugins.Any())
|
||||
{
|
||||
await UpdateManifestAsync();
|
||||
}
|
||||
await UpdateManifestAsync(token);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty, StringComparison.OrdinalIgnoreCase).Trim();
|
||||
|
||||
if (Uri.IsWellFormedUriString(searchNameWithoutKeyword, UriKind.Absolute)
|
||||
&& searchNameWithoutKeyword.Split('.').Last() == zip)
|
||||
return InstallFromWeb(searchNameWithoutKeyword);
|
||||
if (Uri.IsWellFormedUriString(search, UriKind.Absolute)
|
||||
&& search.Split('.').Last() == zip)
|
||||
return InstallFromWeb(search);
|
||||
|
||||
var results =
|
||||
PluginsManifest
|
||||
|
|
@ -408,7 +389,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
ContextData = x
|
||||
});
|
||||
|
||||
return Search(results, searchNameWithoutKeyword);
|
||||
return Search(results, search);
|
||||
}
|
||||
|
||||
private void Install(UserPlugin plugin, string downloadedFilePath)
|
||||
|
|
@ -468,16 +449,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
|
||||
internal List<Result> RequestUninstall(string search)
|
||||
{
|
||||
var autocompletedResults = AutoCompleteReturnAllResults(search,
|
||||
Settings.HotkeyUninstall,
|
||||
"Uninstall",
|
||||
"Select a plugin to uninstall");
|
||||
|
||||
if (autocompletedResults.Any())
|
||||
return autocompletedResults;
|
||||
|
||||
var uninstallSearch = search.Replace(Settings.HotkeyUninstall, string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart();
|
||||
|
||||
var results = Context.API
|
||||
.GetAllPlugins()
|
||||
.Select(x =>
|
||||
|
|
@ -508,7 +479,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
}
|
||||
});
|
||||
|
||||
return Search(results, uninstallSearch);
|
||||
return Search(results, search);
|
||||
}
|
||||
|
||||
private void Uninstall(PluginMetadata plugin, bool removedSetting = true)
|
||||
|
|
@ -523,36 +494,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
|
||||
}
|
||||
|
||||
private List<Result> AutoCompleteReturnAllResults(string search, string hotkey, string title, string subtitle)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(search)
|
||||
&& hotkey.StartsWith(search)
|
||||
&& (hotkey != search || !search.StartsWith(hotkey)))
|
||||
{
|
||||
return
|
||||
new List<Result>
|
||||
{
|
||||
new Result
|
||||
{
|
||||
Title = title,
|
||||
IcoPath = icoPath,
|
||||
SubTitle = subtitle,
|
||||
Action = e =>
|
||||
{
|
||||
Context
|
||||
.API
|
||||
.ChangeQuery(
|
||||
$"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {hotkey} ");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
private bool SameOrLesserPluginVersionExists(string metadataPath)
|
||||
{
|
||||
var newMetadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(metadataPath));
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
{
|
||||
internal class Settings
|
||||
{
|
||||
internal string HotKeyInstall { get; set; } = "install";
|
||||
internal const string InstallCommand = "install";
|
||||
|
||||
internal string HotkeyUninstall { get; set; } = "uninstall";
|
||||
internal const string UninstallCommand = "uninstall";
|
||||
|
||||
internal string HotkeyUpdate { get; set; } = "update";
|
||||
internal const string UpdateCommand = "update";
|
||||
|
||||
public bool WarnFromUnknownSource { get; set; } = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"Name": "Plugins Manager",
|
||||
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
|
||||
"Author": "Jeremy Wu",
|
||||
"Version": "1.11.2",
|
||||
"Version": "1.12.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",
|
||||
|
|
|
|||
Loading…
Reference in a new issue