using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using API.DTOs.Update; using API.SignalR; using API.SignalR.Presence; using Flurl.Http; using Kavita.Common.EnvironmentInfo; using Kavita.Common.Helpers; 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 /// // ReSharper disable once InconsistentNaming 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 /// // ReSharper disable once InconsistentNaming public string Html_Url { get; init; } /// /// Date Release was Published /// // ReSharper disable once InconsistentNaming public string Published_At { get; init; } } public interface IVersionUpdaterService { Task CheckForUpdate(); Task PushUpdate(UpdateNotificationDto update); Task> GetAllReleases(); } public class VersionUpdaterService : IVersionUpdaterService { private readonly ILogger _logger; private readonly IEventHub _eventHub; private readonly IPresenceTracker _tracker; private readonly Markdown _markdown = new MarkdownDeep.Markdown(); #pragma warning disable S1075 private const string GithubLatestReleasesUrl = "https://api.github.com/repos/Kareadita/Kavita/releases/latest"; private const string GithubAllReleasesUrl = "https://api.github.com/repos/Kareadita/Kavita/releases"; #pragma warning restore S1075 public VersionUpdaterService(ILogger logger, IEventHub eventHub, IPresenceTracker tracker) { _logger = logger; _eventHub = eventHub; _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 /// /// Latest update or null if current version is greater than latest update public async Task CheckForUpdate() { var update = await GetGithubRelease(); var dto = CreateDto(update); return new Version(dto.UpdateVersion) <= new Version(dto.CurrentVersion) ? null : dto; } 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 updateVersion = new Version(update.Tag_Name.Replace("v", string.Empty)); var currentVersion = BuildInfo.Version.ToString(); if (updateVersion.Revision == -1) { currentVersion = currentVersion.Substring(0, currentVersion.LastIndexOf(".", StringComparison.Ordinal)); } return new UpdateNotificationDto() { CurrentVersion = currentVersion, UpdateVersion = updateVersion.ToString(), UpdateBody = _markdown.Transform(update.Body.Trim()), UpdateTitle = update.Name, UpdateUrl = update.Html_Url, IsDocker = new OsInfo(Array.Empty()).IsDocker, PublishDate = update.Published_At }; } public async Task PushUpdate(UpdateNotificationDto update) { if (update == null) return; 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 _eventHub.SendMessageAsync(MessageFactory.UpdateAvailable, MessageFactory.UpdateVersionEvent(update), true); } else if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Development) { _logger.LogInformation("Server is up to date. Current: {CurrentVersion}", BuildInfo.Version); await _eventHub.SendMessageAsync(MessageFactory.UpdateAvailable, MessageFactory.UpdateVersionEvent(update), true); } } 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; } }