mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 05:54:26 +01:00
Merge pull request #6896 from btcpayserver/fix/unlisted-plugins-updates
fix: ensure unlisted installed plugins appear as updatable
This commit is contained in:
@@ -26,12 +26,12 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
availablePlugins = await pluginService.GetRemotePlugins(search);
|
availablePlugins = await pluginService.GetRemotePlugins(search);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||||
{
|
{
|
||||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||||
Message = StringLocalizer["Remote plugins lookup failed. Try again later."].Value
|
Message = StringLocalizer["Remote plugins lookup failed. Try again later. Error: {0}", ex.Message].Value
|
||||||
});
|
});
|
||||||
availablePlugins = Array.Empty<PluginService.AvailablePlugin>();
|
availablePlugins = Array.Empty<PluginService.AvailablePlugin>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -46,32 +47,35 @@ namespace BTCPayServer.Plugins
|
|||||||
public JObject ManifestInfo { get; set; }
|
public JObject ManifestInfo { get; set; }
|
||||||
public string Documentation { get; set; }
|
public string Documentation { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record InstalledPluginRequest(string Identifier, string Version);
|
||||||
|
|
||||||
public class PluginBuilderClient
|
public class PluginBuilderClient
|
||||||
{
|
{
|
||||||
HttpClient httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
public HttpClient HttpClient => httpClient;
|
public HttpClient HttpClient => _httpClient;
|
||||||
public PluginBuilderClient(HttpClient httpClient)
|
public PluginBuilderClient(HttpClient httpClient)
|
||||||
{
|
{
|
||||||
this.httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
}
|
}
|
||||||
static JsonSerializerSettings serializerSettings = new() { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() };
|
static JsonSerializerSettings serializerSettings = new() { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() };
|
||||||
public async Task<PublishedVersion[]> GetPublishedVersions(string btcpayVersion, bool includePreRelease, string searchPluginName = null, bool? includeAllVersions = null)
|
public async Task<PublishedVersion[]> GetPublishedVersions(string btcpayVersion, bool includePreRelease, string searchPluginName = null, bool? includeAllVersions = null)
|
||||||
{
|
{
|
||||||
var queryString = $"?includePreRelease={includePreRelease}";
|
var queryString = $"?includePreRelease={includePreRelease}";
|
||||||
if (btcpayVersion is not null)
|
if (btcpayVersion is not null)
|
||||||
queryString += $"&btcpayVersion={btcpayVersion}";
|
queryString += $"&btcpayVersion={Uri.EscapeDataString(btcpayVersion)}";
|
||||||
if (searchPluginName is not null)
|
if (searchPluginName is not null)
|
||||||
queryString += $"&searchPluginName={searchPluginName}";
|
queryString += $"&searchPluginName={Uri.EscapeDataString(searchPluginName)}";
|
||||||
if (includeAllVersions is not null)
|
if (includeAllVersions is not null)
|
||||||
queryString += $"&includeAllVersions={includeAllVersions}";
|
queryString += $"&includeAllVersions={includeAllVersions}";
|
||||||
var result = await httpClient.GetStringAsync($"api/v1/plugins{queryString}");
|
var result = await _httpClient.GetStringAsync($"api/v1/plugins{queryString}");
|
||||||
return JsonConvert.DeserializeObject<PublishedVersion[]>(result, serializerSettings) ?? throw new InvalidOperationException();
|
return JsonConvert.DeserializeObject<PublishedVersion[]>(result, serializerSettings) ?? throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
public async Task<PublishedVersion> GetPlugin(string pluginSlug, string version)
|
public async Task<PublishedVersion> GetPlugin(string pluginSlug, string version)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await httpClient.GetStringAsync($"api/v1/plugins/{pluginSlug}/versions/{version}");
|
var result = await _httpClient.GetStringAsync($"api/v1/plugins/{pluginSlug}/versions/{version}");
|
||||||
return JsonConvert.DeserializeObject<PublishedVersion>(result, serializerSettings);
|
return JsonConvert.DeserializeObject<PublishedVersion>(result, serializerSettings);
|
||||||
}
|
}
|
||||||
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
|
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||||
@@ -85,9 +89,33 @@ namespace BTCPayServer.Plugins
|
|||||||
var queryString = $"?btcpayVersion={btcpayVersion}&includePreRelease={includePreRelease}&includeAllVersions={includeAllVersions}";
|
var queryString = $"?btcpayVersion={btcpayVersion}&includePreRelease={includePreRelease}&includeAllVersions={includeAllVersions}";
|
||||||
var url = $"api/v1/plugins/{identifier}{queryString}";
|
var url = $"api/v1/plugins/{identifier}{queryString}";
|
||||||
|
|
||||||
var result = await httpClient.GetStringAsync(url);
|
var result = await _httpClient.GetStringAsync(url);
|
||||||
return JsonConvert.DeserializeObject<PublishedVersion[]>(result, serializerSettings)
|
return JsonConvert.DeserializeObject<PublishedVersion[]>(result, serializerSettings)
|
||||||
?? throw new InvalidOperationException();
|
?? throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PublishedVersion[]> GetInstalledPluginsUpdates(
|
||||||
|
string btcpayVersion,
|
||||||
|
bool includePreRelease,
|
||||||
|
IEnumerable<InstalledPluginRequest> plugins)
|
||||||
|
{
|
||||||
|
var queryString = $"?includePreRelease={includePreRelease}";
|
||||||
|
if (!string.IsNullOrWhiteSpace(btcpayVersion))
|
||||||
|
queryString += $"&btcpayVersion={Uri.EscapeDataString(btcpayVersion)}";
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(plugins, serializerSettings);
|
||||||
|
using var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
using var resp = await _httpClient.PostAsync($"api/v1/plugins/updates{queryString}", content);
|
||||||
|
resp.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var body = await resp.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<PublishedVersion[]>(body, serializerSettings);
|
||||||
|
|
||||||
|
if (result is null)
|
||||||
|
throw new JsonException("Plugin updates response deserialized to null.");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ 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;
|
||||||
|
|
||||||
public PluginService(
|
public PluginService(
|
||||||
IEnumerable<IBTCPayServerPlugin> btcPayServerPlugins,
|
IEnumerable<IBTCPayServerPlugin> btcPayServerPlugins,
|
||||||
PluginBuilderClient pluginBuilderClient,
|
PluginBuilderClient pluginBuilderClient,
|
||||||
IOptions<DataDirectories> dataDirectories,
|
IOptions<DataDirectories> dataDirectories,
|
||||||
PoliciesSettings policiesSettings,
|
PoliciesSettings policiesSettings,
|
||||||
BTCPayServerEnvironment env)
|
BTCPayServerEnvironment env
|
||||||
|
)
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -49,32 +51,77 @@ namespace BTCPayServer.Plugins
|
|||||||
return pluginManifest.Version;
|
return pluginManifest.Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetShortBtcpayVersion() => Env.Version.TrimStart('v').Split('+')[0];
|
||||||
|
|
||||||
public async Task<AvailablePlugin[]> GetRemotePlugins(string searchPluginName)
|
public async Task<AvailablePlugin[]> GetRemotePlugins(string searchPluginName)
|
||||||
{
|
{
|
||||||
string btcpayVersion = Env.Version.TrimStart('v').Split('+')[0];
|
string btcpayVersion = GetShortBtcpayVersion();
|
||||||
var versions = await _pluginBuilderClient.GetPublishedVersions(
|
var versions = await _pluginBuilderClient.GetPublishedVersions(
|
||||||
btcpayVersion, _policiesSettings.PluginPreReleases, searchPluginName);
|
btcpayVersion, _policiesSettings.PluginPreReleases, searchPluginName);
|
||||||
return versions.Select(v =>
|
|
||||||
|
var plugins = versions
|
||||||
|
.Select(MapToAvailablePlugin)
|
||||||
|
.Where(p => p is not null)
|
||||||
|
.Select(p => p!)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var listedIds = new HashSet<string>(
|
||||||
|
plugins.Select(p => p.Identifier),
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var loadedToCheck = LoadedPlugins
|
||||||
|
.Where(p => !p.SystemPlugin && !listedIds.Contains(p.Identifier))
|
||||||
|
.Select(p => new InstalledPluginRequest(p.Identifier, p.Version.ToString()))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (loadedToCheck.Count <= 0) return plugins.ToArray();
|
||||||
|
|
||||||
|
var updates = await _pluginBuilderClient.GetInstalledPluginsUpdates(
|
||||||
|
btcpayVersion,
|
||||||
|
_policiesSettings.PluginPreReleases,
|
||||||
|
loadedToCheck);
|
||||||
|
|
||||||
|
if (updates is { Length: > 0 })
|
||||||
{
|
{
|
||||||
var p = v.ManifestInfo.ToObject<AvailablePlugin>();
|
plugins.AddRange(
|
||||||
p.Documentation = v.Documentation;
|
updates.Select(MapToAvailablePlugin)
|
||||||
var github = v.BuildInfo.GetGithubRepository();
|
.Where(p => p is not null)
|
||||||
if (github != null)
|
.Select(p => p!)
|
||||||
{
|
);
|
||||||
p.Source = github.GetSourceUrl(v.BuildInfo.gitCommit, v.BuildInfo.pluginDir);
|
}
|
||||||
p.Author = github.Owner;
|
|
||||||
p.AuthorLink = $"https://github.com/{github.Owner}";
|
return plugins.ToArray();
|
||||||
}
|
|
||||||
p.SystemPlugin = false;
|
|
||||||
return p;
|
|
||||||
}).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
private AvailablePlugin? MapToAvailablePlugin(PublishedVersion publishedVersion)
|
||||||
|
{
|
||||||
|
if (publishedVersion.ManifestInfo is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var availablePlugin = publishedVersion.ManifestInfo.ToObject<AvailablePlugin>();
|
||||||
|
if (availablePlugin is null)
|
||||||
|
throw new InvalidDataException($"Manifest deserialized to null BuildId: {publishedVersion.BuildId} PluginSlug: {publishedVersion.ProjectSlug}");
|
||||||
|
|
||||||
|
availablePlugin.Documentation = publishedVersion.Documentation;
|
||||||
|
var buildInfo = publishedVersion.BuildInfo;
|
||||||
|
var github = buildInfo?.GetGithubRepository();
|
||||||
|
if (buildInfo is not null && github is not null)
|
||||||
|
{
|
||||||
|
availablePlugin.Source = github.GetSourceUrl(buildInfo.gitCommit, buildInfo.pluginDir);
|
||||||
|
availablePlugin.Author = github.Owner;
|
||||||
|
availablePlugin.AuthorLink = $"https://github.com/{github.Owner}";
|
||||||
|
}
|
||||||
|
availablePlugin.SystemPlugin = false;
|
||||||
|
return availablePlugin;
|
||||||
|
}
|
||||||
|
#nullable restore
|
||||||
|
|
||||||
public async Task<AvailablePlugin> DownloadRemotePlugin(string pluginIdentifier, string version, VersionCondition condition = null)
|
public async Task<AvailablePlugin> DownloadRemotePlugin(string pluginIdentifier, string version, VersionCondition condition = null)
|
||||||
{
|
{
|
||||||
if (version is null)
|
if (version is null)
|
||||||
{
|
{
|
||||||
string btcpayVersion = Env.Version.TrimStart('v').Split('+')[0];
|
string btcpayVersion = GetShortBtcpayVersion();
|
||||||
var versions = await _pluginBuilderClient.GetPluginVersionsForDownload(pluginIdentifier,
|
var versions = await _pluginBuilderClient.GetPluginVersionsForDownload(pluginIdentifier,
|
||||||
btcpayVersion, _policiesSettings.PluginPreReleases, includeAllVersions: true);
|
btcpayVersion, _policiesSettings.PluginPreReleases, includeAllVersions: true);
|
||||||
var potentialVersions = versions
|
var potentialVersions = versions
|
||||||
|
|||||||
Reference in New Issue
Block a user