Plugins: Support plugin git remote with multiple versions of same plugins

This commit is contained in:
Kukks
2022-01-25 11:15:15 +01:00
parent 74037fd605
commit ecde91ff25
3 changed files with 53 additions and 24 deletions

View File

@@ -83,11 +83,11 @@ namespace BTCPayServer.Controllers
[HttpPost("server/plugins/install")]
public async Task<IActionResult> InstallPlugin(
[FromServices] PluginService pluginService, string plugin, bool update = false)
[FromServices] PluginService pluginService, string plugin, bool update = false, string path ="")
{
try
{
await pluginService.DownloadRemotePlugin(plugin);
await pluginService.DownloadRemotePlugin(plugin, path);
if (update)
{
pluginService.UpdatePlugin(plugin);

View File

@@ -33,23 +33,36 @@ namespace BTCPayServer.Plugins
public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; }
public async Task<IEnumerable<AvailablePlugin>> GetRemotePlugins()
public async Task<AvailablePlugin[]> GetRemotePlugins(string path = "")
{
var resp = await _githubClient
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents/{path}"));
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
return await Task.WhenAll(files.Where(file => file.Name.EndsWith($"{PluginManager.BTCPayPluginSuffix}.json", StringComparison.InvariantCulture)).Select(async file =>
var dirs = files.Where(file => file.Type == "dir");
var result = dirs.Select(file => GetRemotePlugins(file.Path));
var fileTask = Task.WhenAll(files
.Where(file => file.Type == "file" && file.Name.EndsWith($"{PluginManager.BTCPayPluginSuffix}.json",
StringComparison.InvariantCulture)).Select(async file =>
{
return await _githubClient.GetStringAsync(file.DownloadUrl).ContinueWith(
task => JsonConvert.DeserializeObject<AvailablePlugin>(task.Result), TaskScheduler.Current);
task =>
{
var r = JsonConvert.DeserializeObject<AvailablePlugin>(task.Result);
r.Path = path;
return r;
}, TaskScheduler.Current);
}));
return (await Task.WhenAll( result.Concat(new[] { fileTask })).ContinueWith(task => task.Result.SelectMany(plugins => plugins))).ToArray();
}
public async Task DownloadRemotePlugin(string plugin)
public async Task DownloadRemotePlugin(string plugin, string path)
{
var dest = _dataDirectories.Value.PluginDir;
var resp = await _githubClient
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents/{path}"));
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
var ext = files.SingleOrDefault(file => file.Name == $"{plugin}{PluginManager.BTCPayPluginSuffix}");
if (ext is null)
@@ -108,6 +121,7 @@ namespace BTCPayServer.Plugins
public bool SystemPlugin { get; set; } = false;
public IBTCPayServerPlugin.PluginDependency[] Dependencies { get; set; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
public string Path { get; set; }
public void Execute(IApplicationBuilder applicationBuilder,
IServiceProvider applicationBuilderApplicationServices)
@@ -124,6 +138,8 @@ namespace BTCPayServer.Plugins
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("sha")] public string Sha { get; set; }
[JsonProperty("type")] public string Type { get; set; }
[JsonProperty("path")] public string Path { get; set; }
[JsonProperty("download_url")] public string DownloadUrl { get; set; }
}

View File

@@ -1,16 +1,26 @@
@using BTCPayServer.Configuration
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Plugins
@model BTCPayServer.Controllers.UIServerController.ListPluginsViewModel
@inject BTCPayServerOptions BTCPayServerOptions
@{
Layout = "_Layout";
ViewData.SetActivePage(ServerNavPages.Plugins);
var installed = Model.Installed.ToDictionary(plugin => plugin.Identifier.ToLowerInvariant(), plugin => plugin.Version);
var availableAndNotInstalled = Model.Available
var availableAndNotInstalledx = Model.Available
.Where(plugin => !installed.ContainsKey(plugin.Identifier.ToLowerInvariant()))
.OrderBy(plugin => plugin.Identifier)
.GroupBy(plugin => plugin.Identifier)
.ToList();
var availableAndNotInstalled = new List<PluginService.AvailablePlugin>();
foreach (var availableAndNotInstalledItem in availableAndNotInstalledx)
{
var ordered = availableAndNotInstalledItem.OrderByDescending(plugin => plugin.Version).ToArray();
availableAndNotInstalled.Add(ordered.FirstOrDefault(availablePlugin => DependenciesMet(availablePlugin.Dependencies)) ?? ordered.FirstOrDefault());
}
bool DependentOn(string plugin)
{
foreach (var installedPlugin in Model.Installed)
@@ -125,8 +135,10 @@
<div class="row mb-4">
@foreach (var plugin in Model.Installed)
{
var matchedAvailable = Model.Available.SingleOrDefault(availablePlugin => availablePlugin.Identifier == plugin.Identifier);
var updateAvailable = !plugin.SystemPlugin && matchedAvailable != null && plugin.Version < matchedAvailable.Version;
var matchedAvailable = Model.Available.Where(availablePlugin => availablePlugin.Identifier == plugin.Identifier && availablePlugin.Version > plugin.Version).OrderByDescending(availablePlugin => availablePlugin.Version).ToArray();
var x = matchedAvailable.FirstOrDefault(availablePlugin => DependenciesMet(availablePlugin.Dependencies)) ?? matchedAvailable.FirstOrDefault();
var updateAvailable = !plugin.SystemPlugin && matchedAvailable.Any();
var tabId = plugin.Identifier.ToLowerInvariant().Replace(".", "_");
<div class="col col-12 col-lg-6 mb-4">
<div class="card h-100">
@@ -142,7 +154,7 @@
else if (updateAvailable)
{
<div class="badge bg-info ms-2">
@matchedAvailable.Version available
@x.Version available
</div>
}
</h5>
@@ -179,12 +191,12 @@
@if (updateAvailable)
{
<div class="tab-pane" id="@tabId-update">
<p class="card-text">@matchedAvailable.Description</p>
@if (matchedAvailable.Dependencies.Any())
<p class="card-text">@x.Description</p>
@if (x.Dependencies.Any())
{
<h5 class="text-muted">Dependencies</h5>
<ul class="list-group list-group-flush">
@foreach (var dependency in matchedAvailable.Dependencies)
@foreach (var dependency in x.Dependencies)
{
<li class="list-group-item p-2">
@dependency
@@ -203,7 +215,7 @@
@{
var pendingAction = Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
}
@if (!plugin.SystemPlugin && (pendingAction || (updateAvailable && DependenciesMet(matchedAvailable.Dependencies)) || !DependentOn(plugin.Identifier)))
@if (!plugin.SystemPlugin && (pendingAction || (updateAvailable && DependenciesMet(x.Dependencies)) || !DependentOn(plugin.Identifier)))
{
<div class="card-footer border-0 pb-3 d-flex">
@if (pendingAction)
@@ -214,9 +226,9 @@
}
else
{
@if (updateAvailable && DependenciesMet(matchedAvailable.Dependencies))
@if (updateAvailable && DependenciesMet(x.Dependencies))
{
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-update="true" class="me-3">
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-path="@x.Path" asp-route-update="true" class="me-3">
<button type="submit" class="btn btn-secondary">Update</button>
</form>
}
@@ -245,6 +257,7 @@
<div class="row mb-4">
@foreach (var plugin in availableAndNotInstalled)
{
var recommended = BTCPayServerOptions.RecommendedPlugins.Contains(plugin.Identifier.ToLowerInvariant());
var disabled = Model.Disabled?.Contains(plugin.Identifier) ?? false;
@@ -301,7 +314,7 @@
@* Don't show the "Install" button if plugin has been disabled *@
@if (!disabled)
{
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier">
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-path="@plugin.Path">
<button type="submit" class="btn btn-primary">Install</button>
</form>
}