From b761a2c86d63a04f66fd131c4ff37fe9ad61abed Mon Sep 17 00:00:00 2001 From: GreemDev Date: Sun, 8 Jun 2025 17:37:34 -0500 Subject: [PATCH] infra: Custom Update server instead of direct GitLab API calls This reduces the amount of requests for an update from 3 if an update is needed, or 2 if not; to 1 if an update is needed, and none if an update is not. The difference comes from using this update server to check if an update is needed, and not GETing a snippet content for the release channels. --- .../GitLab/GitLabReleaseAssetJsonResponse.cs | 20 ---- .../GitLab/GitLabReleasesJsonResponse.cs | 19 --- src/Ryujinx/Systems/Updater/Updater.GitLab.cs | 108 ++++++++++-------- src/Ryujinx/Systems/Updater/Updater.cs | 10 +- 4 files changed, 60 insertions(+), 97 deletions(-) delete mode 100644 src/Ryujinx/Common/Models/GitLab/GitLabReleaseAssetJsonResponse.cs delete mode 100644 src/Ryujinx/Common/Models/GitLab/GitLabReleasesJsonResponse.cs diff --git a/src/Ryujinx/Common/Models/GitLab/GitLabReleaseAssetJsonResponse.cs b/src/Ryujinx/Common/Models/GitLab/GitLabReleaseAssetJsonResponse.cs deleted file mode 100644 index a5b4bb619..000000000 --- a/src/Ryujinx/Common/Models/GitLab/GitLabReleaseAssetJsonResponse.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Ava.Common.Models.GitLab -{ - public class GitLabReleaseAssetJsonResponse - { - [JsonPropertyName("links")] - public GitLabReleaseAssetLinkJsonResponse[] Links { get; set; } - - public class GitLabReleaseAssetLinkJsonResponse - { - [JsonPropertyName("id")] - public long Id { get; set; } - [JsonPropertyName("name")] - public string AssetName { get; set; } - [JsonPropertyName("url")] - public string Url { get; set; } - } - } -} diff --git a/src/Ryujinx/Common/Models/GitLab/GitLabReleasesJsonResponse.cs b/src/Ryujinx/Common/Models/GitLab/GitLabReleasesJsonResponse.cs deleted file mode 100644 index 8d229a5f1..000000000 --- a/src/Ryujinx/Common/Models/GitLab/GitLabReleasesJsonResponse.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Ava.Common.Models.GitLab -{ - public class GitLabReleasesJsonResponse - { - [JsonPropertyName("name")] - public string Name { get; set; } - - [JsonPropertyName("tag_name")] - public string TagName { get; set; } - - [JsonPropertyName("assets")] - public GitLabReleaseAssetJsonResponse Assets { get; set; } - } - - [JsonSerializable(typeof(GitLabReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)] - public partial class GitLabReleasesJsonSerializerContext : JsonSerializerContext; -} diff --git a/src/Ryujinx/Systems/Updater/Updater.GitLab.cs b/src/Ryujinx/Systems/Updater/Updater.GitLab.cs index 7291ebc16..96afc7fc9 100644 --- a/src/Ryujinx/Systems/Updater/Updater.GitLab.cs +++ b/src/Ryujinx/Systems/Updater/Updater.GitLab.cs @@ -1,15 +1,13 @@ using Gommon; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.Common.Models.GitLab; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Common; using Ryujinx.Common.Helper; using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; using System; -using System.Linq; using System.Net.Http; using System.Net.Http.Json; +using System.Runtime.InteropServices; using System.Text.Json.Serialization; using System.Threading.Tasks; @@ -17,7 +15,31 @@ namespace Ryujinx.Ava.Systems { internal static partial class Updater { - private static GitLabReleaseChannels.ChannelType _currentGitLabReleaseChannel; + private static string CreateUpdateQueryUrl() + { +#pragma warning disable CS8524 + var os = RunningPlatform.CurrentOS switch +#pragma warning restore CS8524 + { + OperatingSystemType.MacOS => "mac", + OperatingSystemType.Linux => "linux", + OperatingSystemType.Windows => "win" + }; + + var arch = RunningPlatform.Architecture switch + { + Architecture.Arm64 => "arm", + Architecture.X64 => "amd64", + _ => null + }; + + if (arch is null) + return null; + + var rc = ReleaseInformation.IsCanaryBuild ? "canary" : "stable"; + + return $"https://update.ryujinx.app/latest/query?os={os}&arch={arch}&rc={rc}"; + } private static async Task> CheckGitLabVersionAsync(bool showVersionUpToDate = false) { @@ -35,38 +57,42 @@ namespace Ryujinx.Ava.Systems return default; } - Logger.Info?.Print(LogClass.Application, "Checking for updates from https://git.ryujinx.app."); + if (CreateUpdateQueryUrl() is not {} updateUrl) + { + Logger.Error?.Print(LogClass.Application, "Could not determine URL for updates."); + + _running = false; + + return default; + } + + Logger.Info?.Print(LogClass.Application, $"Checking for updates from {updateUrl}."); // Get latest version number from GitLab API using HttpClient jsonClient = ConstructHttpClient(); // GitLab instance is located in Ukraine. Connection times will vary across the world. - jsonClient.Timeout = TimeSpan.FromSeconds(10); + jsonClient.Timeout = TimeSpan.FromSeconds(10); - if (_currentGitLabReleaseChannel == null) + try { - GitLabReleaseChannels releaseChannels = await GitLabReleaseChannels.GetAsync(jsonClient); + UpdaterResponse response = + await jsonClient.GetFromJsonAsync(updateUrl, UpdaterResponseJsonContext.Default.UpdaterResponse); - _currentGitLabReleaseChannel = ReleaseInformation.IsCanaryBuild - ? releaseChannels.Canary - : releaseChannels.Stable; + _buildVer = response.Tag; + _buildUrl = response.DownloadUrl; + _changelogUrlFormat = response.ReleaseUrlFormat; + } + catch (Exception e) + { + Logger.Error?.Print(LogClass.Application, $"An error occurred when parsing JSON response from API ({e.GetType().AsFullNamePrettyString()}): {e.Message}"); - Logger.Info?.Print(LogClass.Application, $"Loaded GitLab release channel for '{(ReleaseInformation.IsCanaryBuild ? "canary" : "stable")}'"); - - _changelogUrlFormat = _currentGitLabReleaseChannel.UrlFormat; + _running = false; + return default; } - string fetchedJson = await jsonClient.GetStringAsync(_currentGitLabReleaseChannel.GetLatestReleaseApiUrl()); - GitLabReleasesJsonResponse fetched = JsonHelper.Deserialize(fetchedJson, _glSerializerContext.GitLabReleasesJsonResponse); - - _buildVer = fetched.TagName; - _buildUrl = fetched.Assets.Links - .FirstOrDefault(link => - link.AssetName.StartsWith("ryujinx") && link.AssetName.EndsWith(_platformExt) - )?.Url; - // If build URL not found, assume no new update is available. - if (_buildUrl is null) + if (_buildUrl is null or "") { if (showVersionUpToDate) { @@ -104,35 +130,17 @@ namespace Ryujinx.Ava.Systems return (currentVersion, newVersion); } + + [JsonSerializable(typeof(UpdaterResponse))] + partial class UpdaterResponseJsonContext : JsonSerializerContext; - [JsonSerializable(typeof(GitLabReleaseChannels))] - partial class GitLabReleaseChannelPairContext : JsonSerializerContext; - - public class GitLabReleaseChannels + public class UpdaterResponse { - public static async Task GetAsync(HttpClient httpClient) - => await httpClient.GetFromJsonAsync( - "https://git.ryujinx.app/ryubing/ryujinx/-/snippets/1/raw/main/meta.json", - GitLabReleaseChannelPairContext.Default.GitLabReleaseChannels); + [JsonPropertyName("tag")] public string Tag { get; set; } + [JsonPropertyName("download_url")] public string DownloadUrl { get; set; } + [JsonPropertyName("web_url")] public string ReleaseUrl { get; set; } - [JsonPropertyName("stable")] public ChannelType Stable { get; set; } - [JsonPropertyName("canary")] public ChannelType Canary { get; set; } - - public class ChannelType - { - [JsonPropertyName("id")] public long Id { get; set; } - - [JsonPropertyName("group")] public string Group { get; set; } - - [JsonPropertyName("project")] public string Project { get; set; } - - public string UrlFormat => $"https://git.ryujinx.app/{ToString()}/-/releases/{{0}}"; - - public override string ToString() => $"{Group}/{Project}"; - - public string GetLatestReleaseApiUrl() => - $"https://git.ryujinx.app/api/v4/projects/{Id}/releases/permalink/latest"; - } + [JsonIgnore] public string ReleaseUrlFormat => ReleaseUrl.Replace(Tag, "{0}"); } } } diff --git a/src/Ryujinx/Systems/Updater/Updater.cs b/src/Ryujinx/Systems/Updater/Updater.cs index 59f94e61a..f4d49a1d1 100644 --- a/src/Ryujinx/Systems/Updater/Updater.cs +++ b/src/Ryujinx/Systems/Updater/Updater.cs @@ -6,7 +6,6 @@ using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Zip; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Models.Github; -using Ryujinx.Ava.Common.Models.GitLab; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.Utilities; using Ryujinx.Common; @@ -33,19 +32,14 @@ namespace Ryujinx.Ava.Systems internal static partial class Updater { private static readonly GithubReleasesJsonSerializerContext _ghSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - private static readonly GitLabReleasesJsonSerializerContext _glSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - + private static readonly string _platformExt = BuildPlatformExtension(); private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); private const int ConnectionCount = 4; private static string _buildVer; - - - private static readonly string _platformExt = BuildPlatformExtension(); - private static string _buildUrl; private static long _buildSize; private static bool _updateSuccessful; @@ -168,7 +162,7 @@ namespace Ryujinx.Ava.Systems HttpClient result = new(); // Required by GitHub to interact with APIs. - result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); + result.DefaultRequestHeaders.Add("User-Agent", $"Ryujinx-Updater/{ReleaseInformation.Version}"); return result; }