mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 22:44:29 +01:00
refactor: add parallel checks and DRY mapper
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Contracts;
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
using BTCPayServer.Configuration;
|
using BTCPayServer.Configuration;
|
||||||
@@ -9,6 +11,7 @@ using BTCPayServer.Services;
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
@@ -20,6 +23,7 @@ namespace BTCPayServer.Plugins
|
|||||||
private readonly IOptions<DataDirectories> _dataDirectories;
|
private readonly IOptions<DataDirectories> _dataDirectories;
|
||||||
private readonly PoliciesSettings _policiesSettings;
|
private readonly PoliciesSettings _policiesSettings;
|
||||||
private readonly PluginBuilderClient _pluginBuilderClient;
|
private readonly PluginBuilderClient _pluginBuilderClient;
|
||||||
|
private readonly ILogger<PluginService> _logger;
|
||||||
private static readonly HashSet<string> _builtInPluginIdentifiers = new(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> _builtInPluginIdentifiers = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
"BTCPayServer",
|
"BTCPayServer",
|
||||||
@@ -37,7 +41,9 @@ namespace BTCPayServer.Plugins
|
|||||||
PluginBuilderClient pluginBuilderClient,
|
PluginBuilderClient pluginBuilderClient,
|
||||||
IOptions<DataDirectories> dataDirectories,
|
IOptions<DataDirectories> dataDirectories,
|
||||||
PoliciesSettings policiesSettings,
|
PoliciesSettings policiesSettings,
|
||||||
BTCPayServerEnvironment env)
|
BTCPayServerEnvironment env,
|
||||||
|
ILogger<PluginService> logger
|
||||||
|
)
|
||||||
{
|
{
|
||||||
LoadedPlugins = btcPayServerPlugins;
|
LoadedPlugins = btcPayServerPlugins;
|
||||||
Installed = btcPayServerPlugins.ToDictionary(p => p.Identifier, p => p.Version, StringComparer.OrdinalIgnoreCase);
|
Installed = btcPayServerPlugins.ToDictionary(p => p.Identifier, p => p.Version, StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -45,6 +51,7 @@ namespace BTCPayServer.Plugins
|
|||||||
_dataDirectories = dataDirectories;
|
_dataDirectories = dataDirectories;
|
||||||
_policiesSettings = policiesSettings;
|
_policiesSettings = policiesSettings;
|
||||||
Env = env;
|
Env = env;
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, Version> Installed { get; set; }
|
public Dictionary<string, Version> Installed { get; set; }
|
||||||
@@ -67,20 +74,10 @@ namespace BTCPayServer.Plugins
|
|||||||
var versions = await _pluginBuilderClient.GetPublishedVersions(
|
var versions = await _pluginBuilderClient.GetPublishedVersions(
|
||||||
btcpayVersion, _policiesSettings.PluginPreReleases, searchPluginName);
|
btcpayVersion, _policiesSettings.PluginPreReleases, searchPluginName);
|
||||||
|
|
||||||
var plugins = versions.Select(v =>
|
var plugins = versions
|
||||||
{
|
.Select(MapToAvailablePlugin)
|
||||||
var p = v.ManifestInfo.ToObject<AvailablePlugin>();
|
.Where(p => p is not null)
|
||||||
p.Documentation = v.Documentation;
|
.ToList()!;
|
||||||
var github = v.BuildInfo.GetGithubRepository();
|
|
||||||
if (github != null)
|
|
||||||
{
|
|
||||||
p.Source = github.GetSourceUrl(v.BuildInfo.gitCommit, v.BuildInfo.pluginDir);
|
|
||||||
p.Author = github.Owner;
|
|
||||||
p.AuthorLink = $"https://github.com/{github.Owner}";
|
|
||||||
}
|
|
||||||
p.SystemPlugin = false;
|
|
||||||
return p;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var unlistedUpdates = await GetUpdatesForUnlistedInstalledAsync(plugins, btcpayVersion);
|
var unlistedUpdates = await GetUpdatesForUnlistedInstalledAsync(plugins, btcpayVersion);
|
||||||
plugins.AddRange(unlistedUpdates);
|
plugins.AddRange(unlistedUpdates);
|
||||||
@@ -89,72 +86,101 @@ namespace BTCPayServer.Plugins
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<AvailablePlugin>> GetUpdatesForUnlistedInstalledAsync(
|
private async Task<List<AvailablePlugin>> GetUpdatesForUnlistedInstalledAsync(
|
||||||
List<AvailablePlugin> publishedPlugins,
|
List<AvailablePlugin> listedPlugins,
|
||||||
string btcpayVersion)
|
string btcpayVersion,
|
||||||
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var updatables = new List<AvailablePlugin>();
|
var listedIdentifiers = new HashSet<string>(
|
||||||
var presentIdentifiers = new HashSet<string>(
|
listedPlugins.Select(p => p.Identifier),
|
||||||
publishedPlugins.Select(p => p.Identifier),
|
|
||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach (var (installedIdentifier, installedVersion) in Installed)
|
var installedToCheck = Installed
|
||||||
|
.Where(installedPlugin => !_builtInPluginIdentifiers.Contains(installedPlugin.Key) &&
|
||||||
|
!listedIdentifiers.Contains(installedPlugin.Key))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var results = new ConcurrentBag<AvailablePlugin>();
|
||||||
|
|
||||||
|
var parallelOpts = new ParallelOptions
|
||||||
{
|
{
|
||||||
if (_builtInPluginIdentifiers.Contains(installedIdentifier))
|
MaxDegreeOfParallelism = Math.Min(6, Environment.ProcessorCount),
|
||||||
continue;
|
CancellationToken = ct
|
||||||
|
};
|
||||||
|
|
||||||
if (presentIdentifiers.Contains(installedIdentifier))
|
await Parallel.ForEachAsync(installedToCheck, parallelOpts, async (installedPlugin, ct2) =>
|
||||||
continue;
|
{
|
||||||
|
var (installedIdentifier, installedVersion) = installedPlugin;
|
||||||
|
|
||||||
var publishedVersions = await _pluginBuilderClient.GetPluginVersionsForDownload(
|
try
|
||||||
installedIdentifier,
|
|
||||||
btcpayVersion,
|
|
||||||
_policiesSettings.PluginPreReleases,
|
|
||||||
includeAllVersions: true);
|
|
||||||
|
|
||||||
if (publishedVersions == null || !publishedVersions.Any())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var candidates = publishedVersions
|
|
||||||
.Select(versionInfo => new
|
|
||||||
{
|
|
||||||
VersionInfo = versionInfo,
|
|
||||||
Available = versionInfo.ManifestInfo?.ToObject<AvailablePlugin>()
|
|
||||||
})
|
|
||||||
.Where(candidate => candidate.Available != null &&
|
|
||||||
candidate.Available.Identifier.Equals(installedIdentifier, StringComparison.OrdinalIgnoreCase))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (candidates.Count == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var latestCandidate = candidates
|
|
||||||
.OrderByDescending(candidate => candidate.Available!.Version)
|
|
||||||
.First();
|
|
||||||
|
|
||||||
var latestAvailable = latestCandidate.Available!;
|
|
||||||
|
|
||||||
|
|
||||||
if (latestAvailable.Version <= installedVersion)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
latestAvailable.Documentation = latestCandidate.VersionInfo.Documentation;
|
|
||||||
|
|
||||||
var githubRepository = latestCandidate.VersionInfo.BuildInfo.GetGithubRepository();
|
|
||||||
if (githubRepository != null)
|
|
||||||
{
|
{
|
||||||
latestAvailable.Source = githubRepository.GetSourceUrl(
|
var publishedVersions = await _pluginBuilderClient.GetPluginVersionsForDownload(
|
||||||
latestCandidate.VersionInfo.BuildInfo.gitCommit,
|
installedIdentifier,
|
||||||
latestCandidate.VersionInfo.BuildInfo.pluginDir);
|
btcpayVersion,
|
||||||
latestAvailable.Author = githubRepository.Owner;
|
_policiesSettings.PluginPreReleases,
|
||||||
latestAvailable.AuthorLink = $"https://github.com/{githubRepository.Owner}";
|
includeAllVersions: true);
|
||||||
|
|
||||||
|
if (publishedVersions == null || !publishedVersions.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var latestCandidate = publishedVersions
|
||||||
|
.Select(publishedVersion => (VersionInfo: publishedVersion, Manifest: publishedVersion.ManifestInfo))
|
||||||
|
.Where(versionTuple => versionTuple.Manifest != null)
|
||||||
|
.Select(versionTuple =>
|
||||||
|
{
|
||||||
|
var identifier = versionTuple.Manifest!["Identifier"]?.ToString();
|
||||||
|
var parsedVersion = Version.TryParse(versionTuple.Manifest!["Version"]?.ToString(), out var ver) ? ver : null;
|
||||||
|
|
||||||
|
return new { versionTuple.VersionInfo, Identifier = identifier, Version = parsedVersion };
|
||||||
|
})
|
||||||
|
.Where(candidate => candidate.Identifier != null &&
|
||||||
|
candidate.Identifier.Equals(installedIdentifier, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
candidate.Version is not null)
|
||||||
|
.OrderByDescending(candidate => candidate.Version)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (latestCandidate == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var latestAvailable = MapToAvailablePlugin(latestCandidate.VersionInfo);
|
||||||
|
|
||||||
|
if (latestAvailable is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (latestAvailable.Version <= installedVersion)
|
||||||
|
return;
|
||||||
|
|
||||||
|
results.Add(latestAvailable);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex,
|
||||||
|
"Error while checking for updates for installed plugin {InstalledPluginIdentifier}",
|
||||||
|
installedIdentifier);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
latestAvailable.SystemPlugin = false;
|
return results.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
updatables.Add(latestAvailable);
|
private AvailablePlugin MapToAvailablePlugin(PublishedVersion publishedVersion)
|
||||||
|
{
|
||||||
|
var availablePlugin = publishedVersion.ManifestInfo.ToObject<AvailablePlugin>();
|
||||||
|
if (availablePlugin is null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("ManifestInfo missing for published version {Version}", publishedVersion.ToString());
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatables;
|
availablePlugin.Documentation = publishedVersion.Documentation;
|
||||||
|
var github = publishedVersion.BuildInfo?.GetGithubRepository();
|
||||||
|
if (github != null)
|
||||||
|
{
|
||||||
|
availablePlugin.Source = github.GetSourceUrl(publishedVersion.BuildInfo.gitCommit, publishedVersion.BuildInfo.pluginDir);
|
||||||
|
availablePlugin.Author = github.Owner;
|
||||||
|
availablePlugin.AuthorLink = $"https://github.com/{github.Owner}";
|
||||||
|
}
|
||||||
|
availablePlugin.SystemPlugin = false;
|
||||||
|
return availablePlugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AvailablePlugin> DownloadRemotePlugin(string pluginIdentifier, string version, VersionCondition condition = null)
|
public async Task<AvailablePlugin> DownloadRemotePlugin(string pluginIdentifier, string version, VersionCondition condition = null)
|
||||||
|
|||||||
Reference in New Issue
Block a user