infra: Use Ryujinx.UpdateClient NuGet package for checking for updates.

Main benefit to this is sharing the C# model definitions from what the server returns and Ryujinx uses in-app without differences.
Additionally removed the GitHub API JSON models.
This commit is contained in:
GreemDev 2025-06-19 04:18:02 -05:00
parent 6226eadf55
commit 6773406bb6
8 changed files with 38 additions and 101 deletions

View file

@ -42,6 +42,8 @@
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.20.0" /> <PackageVersion Include="Ryujinx.LibHac" Version="0.20.0" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" /> <PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.28" />
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.28" />
<PackageVersion Include="Gommon" Version="2.7.1.1" /> <PackageVersion Include="Gommon" Version="2.7.1.1" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.6.0" /> <PackageVersion Include="Sep" Version="0.6.0" />

View file

@ -10,11 +10,13 @@
</packageSources> </packageSources>
<packageSourceMapping> <packageSourceMapping>
<!-- key value for <packageSource> should match key values from <packageSources> element --> <!-- key value for <packageSource> should match key values from <packageSources> element -->
<!-- These are defined and .NET still yells about multiple package sources with no mappings. Not sure what to do, this is in the docs lol -->
<packageSource key="nuget.org"> <packageSource key="nuget.org">
<package pattern="*" /> <package pattern="*" />
</packageSource> </packageSource>
<packageSource key="Ryujinx.UpdateClient"> <packageSource key="Ryujinx.UpdateClient">
<package pattern="Ryujinx.UpdateClient" /> <package pattern="Ryujinx.UpdateClient" />
<package pattern="Ryujinx.Systems.Update.Common" />
</packageSource> </packageSource>
<!--<packageSource key="LibHacAlpha"> <!--<packageSource key="LibHacAlpha">
<package pattern="Ryujinx.LibHac" /> <package pattern="Ryujinx.LibHac" />

View file

@ -1,9 +0,0 @@
namespace Ryujinx.Ava.Common.Models.Github
{
public class GithubReleaseAssetJsonResponse
{
public string Name { get; set; }
public string State { get; set; }
public string BrowserDownloadUrl { get; set; }
}
}

View file

@ -1,12 +0,0 @@
using System.Collections.Generic;
namespace Ryujinx.Ava.Common.Models.Github
{
public class GithubReleasesJsonResponse
{
public string Name { get; set; }
public string TagName { get; set; }
public List<GithubReleaseAssetJsonResponse> Assets { get; set; }
}
}

View file

@ -1,7 +0,0 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Ava.Common.Models.Github
{
[JsonSerializable(typeof(GithubReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)]
public partial class GithubReleasesJsonSerializerContext : JsonSerializerContext;
}

View file

@ -65,6 +65,8 @@
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" /> <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" /> <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'" /> <PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'" />
<PackageReference Include="Ryujinx.UpdateClient" />
<PackageReference Include="Ryujinx.Systems.Update.Common" />
<PackageReference Include="securifybv.ShellLink" /> <PackageReference Include="securifybv.ShellLink" />
<PackageReference Include="Sep" /> <PackageReference Include="Sep" />
<PackageReference Include="Silk.NET.Vulkan" /> <PackageReference Include="Silk.NET.Vulkan" />

View file

@ -4,42 +4,27 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Helper; using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Systems.Update.Client;
using Ryujinx.Systems.Update.Common;
using Ryujinx.Systems.Updater.Common;
using System; using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.Systems namespace Ryujinx.Ava.Systems
{ {
internal static partial class Updater internal static partial class Updater
{ {
private static string CreateUpdateQueryUrl() private static VersionResponse _versionResponse;
{
#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 private static UpdateClient CreateUpdateClient()
{ => UpdateClient.Builder()
Architecture.Arm64 => "arm", .WithServerEndpoint("https://update.ryujinx.app") // This is the default, and doesn't need to be provided; it's here for transparency.
Architecture.X64 => "amd64", .WithLogger((format, args, caller) =>
_ => null Logger.Info?.Print(
}; LogClass.Application,
args.Length is 0 ? format : format.Format(args),
if (arch is null) caller: caller)
return null; );
var rc = ReleaseInformation.IsCanaryBuild ? "canary" : "stable";
return $"https://update.ryujinx.app/latest/query?os={os}&arch={arch}&rc={rc}";
}
public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false) public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false)
{ {
@ -57,39 +42,31 @@ namespace Ryujinx.Ava.Systems
return default; return default;
} }
if (CreateUpdateQueryUrl() is not {} updateUrl) using UpdateClient updateClient = CreateUpdateClient();
{
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 update.ryujinx.app API
using HttpClient jsonClient = ConstructHttpClient();
try try
{ {
UpdaterResponse response = _versionResponse = await updateClient.QueryLatestAsync(ReleaseInformation.IsCanaryBuild
await jsonClient.GetFromJsonAsync(updateUrl, UpdaterResponseJsonContext.Default.UpdaterResponse); ? ReleaseChannel.Canary
: ReleaseChannel.Stable);
_buildVer = response.Tag;
_buildUrl = response.DownloadUrl;
_changelogUrlFormat = response.ReleaseUrlFormat;
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Error?.Print(LogClass.Application, $"An error occurred when parsing JSON response from API ({e.GetType().AsFullNamePrettyString()}): {e.Message}"); Logger.Error?.Print(LogClass.Application, $"An error occurred when requesting for updates ({e.GetType().AsFullNamePrettyString()}): {e.Message}");
_running = false; _running = false;
return default; return default;
} }
if (_versionResponse == null)
{
// logging is done via the UpdateClient library
_running = false;
return default;
}
// If build URL not found, assume no new update is available. // If build URL not found, assume no new update is available.
if (_buildUrl is null or "") if (_versionResponse.ArtifactUrl is null or "")
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
@ -99,7 +76,7 @@ namespace Ryujinx.Ava.Systems
if (userResult is UserResult.Ok) if (userResult is UserResult.Ok)
{ {
OpenHelper.OpenUrl(_changelogUrlFormat.Format(currentVersion)); OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion));
} }
} }
@ -111,7 +88,7 @@ namespace Ryujinx.Ava.Systems
} }
if (!Version.TryParse(_buildVer, out Version newVersion)) if (!Version.TryParse(_versionResponse.Version, out Version newVersion))
{ {
Logger.Error?.Print(LogClass.Application, Logger.Error?.Print(LogClass.Application,
$"Failed to convert the received {RyujinxApp.FullAppName} version from the update server!"); $"Failed to convert the received {RyujinxApp.FullAppName} version from the update server!");
@ -127,17 +104,5 @@ namespace Ryujinx.Ava.Systems
return (currentVersion, newVersion); return (currentVersion, newVersion);
} }
[JsonSerializable(typeof(UpdaterResponse))]
partial class UpdaterResponseJsonContext : JsonSerializerContext;
public class UpdaterResponse
{
[JsonPropertyName("tag")] public string Tag { get; set; }
[JsonPropertyName("download_url")] public string DownloadUrl { get; set; }
[JsonPropertyName("web_url")] public string ReleaseUrl { get; set; }
[JsonIgnore] public string ReleaseUrlFormat => ReleaseUrl.Replace(Tag, "{0}");
}
} }
} }

View file

@ -5,13 +5,11 @@ using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models.Github;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Helper; using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -36,16 +34,12 @@ namespace Ryujinx.Ava.Systems
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
private const int ConnectionCount = 4; private const int ConnectionCount = 4;
private static string _buildVer;
private static string _buildUrl;
private static long _buildSize; private static long _buildSize;
private static bool _updateSuccessful; private static bool _updateSuccessful;
private static bool _running; private static bool _running;
private static readonly string[] _windowsDependencyDirs = []; private static readonly string[] _windowsDependencyDirs = [];
private static string _changelogUrlFormat = null;
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false) public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
{ {
if (_running) if (_running)
@ -72,7 +66,7 @@ namespace Ryujinx.Ava.Systems
if (userResult is UserResult.Ok) if (userResult is UserResult.Ok)
{ {
OpenHelper.OpenUrl(_changelogUrlFormat.Format(currentVersion)); OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion));
} }
} }
@ -92,7 +86,7 @@ namespace Ryujinx.Ava.Systems
// GitLab instance is located in Ukraine. Connection times will vary across the world. // GitLab instance is located in Ukraine. Connection times will vary across the world.
buildSizeClient.Timeout = TimeSpan.FromSeconds(10); buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead); HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
_buildSize = message.Content.Headers.ContentRange.Length.Value; _buildSize = message.Content.Headers.ContentRange.Length.Value;
} }
@ -122,7 +116,7 @@ namespace Ryujinx.Ava.Systems
switch (shouldUpdate) switch (shouldUpdate)
{ {
case UserResult.Yes: case UserResult.Yes:
await UpdateRyujinx(_buildUrl); await UpdateRyujinx(_versionResponse.ArtifactUrl);
break; break;
// Secondary button maps to no, which in this case is the show changelog button. // Secondary button maps to no, which in this case is the show changelog button.
case UserResult.No: case UserResult.No: