using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using API.DTOs.Update; using API.Interfaces.Services; using API.SignalR; using API.SignalR.Presence; using Flurl.Http; using Flurl.Http.Configuration; using Kavita.Common.EnvironmentInfo; using MarkdownDeep; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace API.Services.Tasks { internal class GithubReleaseMetadata { /// /// Name of the Tag /// v0.4.3 /// public string Tag_Name { get; init; } /// /// Name of the Release /// public string Name { get; init; } /// /// Body of the Release /// public string Body { get; init; } /// /// Url of the release on Github /// public string Html_Url { get; init; } } public class UntrustedCertClientFactory : DefaultHttpClientFactory { public override HttpMessageHandler CreateMessageHandler() { return new HttpClientHandler { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }; } } public class VersionUpdaterService : IVersionUpdaterService { private readonly ILogger _logger; private readonly IHubContext _messageHub; private readonly IPresenceTracker _tracker; private readonly Markdown _markdown = new MarkdownDeep.Markdown(); private static readonly string GithubLatestReleasesUrl = "https://api.github.com/repos/Kareadita/Kavita/releases/latest"; private static readonly string GithubAllReleasesUrl = "https://api.github.com/repos/Kareadita/Kavita/releases"; public VersionUpdaterService(ILogger logger, IHubContext messageHub, IPresenceTracker tracker) { _logger = logger; _messageHub = messageHub; _tracker = tracker; FlurlHttp.ConfigureClient(GithubLatestReleasesUrl, cli => cli.Settings.HttpClientFactory = new UntrustedCertClientFactory()); FlurlHttp.ConfigureClient(GithubAllReleasesUrl, cli => cli.Settings.HttpClientFactory = new UntrustedCertClientFactory()); } /// /// Fetches the latest release from Github /// public async Task CheckForUpdate() { var update = await GetGithubRelease(); return CreateDto(update); } /// /// /// /// public async Task> GetAllReleases() { var updates = await GetGithubReleases(); return updates.Select(CreateDto); } private UpdateNotificationDto CreateDto(GithubReleaseMetadata update) { if (update == null || string.IsNullOrEmpty(update.Tag_Name)) return null; var version = update.Tag_Name.Replace("v", string.Empty); var updateVersion = new Version(version); return new UpdateNotificationDto() { CurrentVersion = version, UpdateVersion = updateVersion.ToString(), UpdateBody = _markdown.Transform(update.Body.Trim()), UpdateTitle = update.Name, UpdateUrl = update.Html_Url, IsDocker = new OsInfo(Array.Empty()).IsDocker }; } public async Task PushUpdate(UpdateNotificationDto update) { if (update == null) return; var admins = await _tracker.GetOnlineAdmins(); var updateVersion = new Version(update.CurrentVersion); if (BuildInfo.Version < updateVersion) { _logger.LogInformation("Server is out of date. Current: {CurrentVersion}. Available: {AvailableUpdate}", BuildInfo.Version, updateVersion); await SendEvent(update, admins); } else if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Development) { _logger.LogInformation("Server is up to date. Current: {CurrentVersion}", BuildInfo.Version); await SendEvent(update, admins); } } private async Task SendEvent(UpdateNotificationDto update, IReadOnlyList admins) { var connections = new List(); foreach (var admin in admins) { connections.AddRange(await _tracker.GetConnectionsForUser(admin)); } await _messageHub.Clients.Users(admins).SendAsync("UpdateAvailable", new SignalRMessage { Name = "UpdateAvailable", Body = update }); } private static async Task GetGithubRelease() { var update = await GithubLatestReleasesUrl .WithHeader("Accept", "application/json") .WithHeader("User-Agent", "Kavita") .GetJsonAsync(); return update; } private static async Task> GetGithubReleases() { var update = await GithubAllReleasesUrl .WithHeader("Accept", "application/json") .WithHeader("User-Agent", "Kavita") .GetJsonAsync>(); return update; } } }