Skip to content

Commit 2b0c526

Browse files
committed
New endpoint: /api/v1/meta
Allows you to see the gitlab project info about where the updates come from, mostly useful for RyujinxHelper.
1 parent f496e26 commit 2b0c526

7 files changed

Lines changed: 122 additions & 22 deletions

File tree

src/Client/Program.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This is used for testing the API as it's being made.
22

3-
/*using Ryujinx.Systems.Update.Client;
3+
//using System.Text.Json;
4+
//using Ryujinx.Systems.Update.Client;
45

5-
var uc = new UpdateClient(UpdateClientConfig.WithAuthorization("redacted"));*/
6+
//var uc = new UpdateClient(UpdateClientConfig.StandardUnauthorized("http://localhost:5000"));
67

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Net.Http.Json;
2+
using Ryujinx.Systems.Update.Common;
3+
4+
namespace Ryujinx.Systems.Update.Client;
5+
6+
public partial class UpdateClient
7+
{
8+
/// <summary>
9+
/// Query the sources of the configured version caches on the server. Does not require admin token.
10+
/// </summary>
11+
/// <returns>A <see cref="Dictionary{string,VersionCacheSource}"/>, or null if any non-200 series HTTP status code is returned from the server.</returns>
12+
public async Task<Dictionary<string, VersionCacheSource>?> QueryCacheSourcesAsync()
13+
{
14+
Log("Checking for cache sources from: {0}", [QualifyUriPath(Constants.FullRouteName_Api_Meta)]);
15+
16+
var resp = await _http.GetAsync(Constants.FullRouteName_Api_Meta);
17+
18+
if (!resp.IsSuccessStatusCode)
19+
{
20+
Log("Received non-success status code ({0}) for cache sources query!",
21+
[Enum.GetName(resp.StatusCode) ?? $"{(int)resp.StatusCode}"]);
22+
return null;
23+
}
24+
25+
return await resp.Content.ReadFromJsonAsync(JsonSerializerContexts.Default.DictionaryStringVersionCacheSource);
26+
}
27+
}

src/Common/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ internal static class Constants
1818
public const string RouteName_Download = "download";
1919
public const string RouteName_Latest = "latest";
2020
public const string RouteName_Api_Version = "version";
21+
public const string RouteName_Api_Meta = "meta";
2122
public const string RouteName_Api_Admin = "admin";
2223
public const string RouteName_Api_Admin_RefreshCache = "refresh_cache";
2324

25+
public const string FullRouteName_Api_Meta = $"{FullApiPrefix}{RouteName_Api_Meta}";
2426
public const string FullRouteName_Api_Version = $"{FullApiPrefix}{RouteName_Api_Version}";
2527
public const string FullRouteName_Api_Admin_RefreshCache = $"{FullApiPrefix}{RouteName_Api_Admin}/{RouteName_Api_Admin_RefreshCache}";
2628
}

src/Common/JsonSerializerContexts.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Ryujinx.Systems.Update.Common;
44

5-
[JsonSerializable(typeof(VersionCacheEntry))]
6-
[JsonSerializable(typeof(VersionResponse))]
5+
[JsonSerializable(typeof(VersionCacheEntry[]))]
6+
[JsonSerializable(typeof(VersionResponse[]))]
7+
[JsonSerializable(typeof(IEnumerable<VersionCacheEntry>))]
8+
[JsonSerializable(typeof(IEnumerable<VersionResponse>))]
9+
[JsonSerializable(typeof(Dictionary<string, VersionCacheSource>))]
710
public partial class JsonSerializerContexts : JsonSerializerContext;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Ryujinx.Systems.Update.Common;
4+
5+
public class VersionCacheSource
6+
{
7+
internal static VersionCacheSource Empty => new()
8+
{
9+
Id = 0,
10+
Owner = string.Empty,
11+
Project = string.Empty
12+
};
13+
14+
[JsonPropertyName("id")] public required long Id { get; set; }
15+
[JsonPropertyName("owner")] public required string Owner { get; set; }
16+
[JsonPropertyName("project")] public required string Project { get; set; }
17+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Ryujinx.Systems.Update.Common;
3+
using Ryujinx.Systems.Update.Server.Services.GitLab;
4+
5+
namespace Ryujinx.Systems.Update.Server.Controllers;
6+
7+
[Route($"{Constants.FullRouteName_Api_Meta}")]
8+
[ApiController]
9+
public class MetaController : ControllerBase
10+
{
11+
private static readonly Dictionary<string, VersionCacheSource> CacheSources =
12+
new(2)
13+
{
14+
{ "stable", VersionCacheSource.Empty },
15+
{ "canary", VersionCacheSource.Empty }
16+
};
17+
18+
[HttpGet]
19+
[ProducesResponseType(StatusCodes.Status200OK)]
20+
[Produces("application/json")]
21+
public async Task<ActionResult<Dictionary<string, VersionCacheSource>>> Action()
22+
{
23+
var stableCache = HttpContext.RequestServices.GetRequiredKeyedService<VersionCache>("stableCache");
24+
25+
if (!stableCache.HasProjectInfo)
26+
return BadRequest("Stable cache isn't initialized yet.");
27+
28+
CacheSources["stable"] = new VersionCacheSource
29+
{
30+
Id = stableCache.ProjectId,
31+
Owner = stableCache.ProjectPath.Split('/')[0],
32+
Project = stableCache.ProjectPath.Split('/')[1]
33+
};
34+
35+
var canaryCache = HttpContext.RequestServices.GetKeyedService<VersionCache>("canaryCache");
36+
37+
if (canaryCache?.HasProjectInfo ?? false)
38+
{
39+
CacheSources["canary"] = new VersionCacheSource
40+
{
41+
Id = canaryCache.ProjectId,
42+
Owner = canaryCache.ProjectPath.Split('/')[0],
43+
Project = canaryCache.ProjectPath.Split('/')[1]
44+
};
45+
}
46+
47+
return Ok(CacheSources);
48+
}
49+
}

src/Server/Services/GitLab/VersionCache.cs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ public class VersionCache : SafeDictionary<string, VersionCacheEntry>
1212
private readonly ILogger<VersionCache> _logger;
1313
private readonly PeriodicTimer? _refreshTimer;
1414

15-
private (string Name, long Id, string Path)? _cachedProject;
15+
private Project? _cachedProject;
16+
17+
public bool HasProjectInfo => _cachedProject != null;
18+
19+
public string ProjectName => _cachedProject!.NameWithNamespace;
20+
public long ProjectId => _cachedProject!.Id;
21+
public string ProjectPath => _cachedProject!.PathWithNamespace;
1622

1723
private string? _latestTag;
1824

@@ -52,18 +58,13 @@ public VersionCache(IConfiguration config, GitLabService gitlabService, ILogger<
5258
}
5359
}
5460

55-
public string ReleaseUrlFormat => $"{_gitlabEndpoint.TrimEnd('/')}/{_cachedProject!.Value.Path}/-/releases/{{0}}";
61+
public string ReleaseUrlFormat => $"{_gitlabEndpoint.TrimEnd('/')}/{ProjectPath}/-/releases/{{0}}";
5662

5763
public void Init(ProjectId projectId) => Executor.ExecuteBackgroundAsync(async () =>
5864
{
5965
try
6066
{
61-
if (!_cachedProject.HasValue)
62-
{
63-
var project = await _gl.Client.Projects.GetAsync(projectId);
64-
65-
_cachedProject = (project.NameWithNamespace, project.Id, project.PathWithNamespace);
66-
}
67+
_cachedProject ??= await _gl.Client.Projects.GetAsync(projectId);
6768
}
6869
catch (GitLabException e)
6970
{
@@ -73,7 +74,7 @@ public void Init(ProjectId projectId) => Executor.ExecuteBackgroundAsync(async (
7374
return;
7475
}
7576

76-
_logger.LogInformation("Initializing version cache for {project}", _cachedProject!.Value.Name);
77+
_logger.LogInformation("Initializing version cache for {project}", ProjectName);
7778

7879
await RefreshAsync();
7980

@@ -85,12 +86,12 @@ public void Init(ProjectId projectId) => Executor.ExecuteBackgroundAsync(async (
8586

8687
_logger.LogInformation(
8788
"Periodic version cache refreshing is disabled for {project}. It can be refreshed by {means}",
88-
_cachedProject!.Value.Name, howToRefresh);
89+
ProjectName, howToRefresh);
8990
return;
9091
}
9192

9293
_logger.LogInformation("Refreshing version cache for {project} every {timePeriod} minutes.",
93-
_cachedProject!.Value.Name, _refreshTimer.Period.TotalMinutes);
94+
ProjectName, _refreshTimer.Period.TotalMinutes);
9495
while (await _refreshTimer.WaitForNextTickAsync())
9596
{
9697
await RefreshAsync();
@@ -111,19 +112,19 @@ public void Init(ProjectId projectId) => Executor.ExecuteBackgroundAsync(async (
111112

112113
public async Task RefreshAsync()
113114
{
114-
_logger.LogInformation("Reloading version cache for {project}", _cachedProject!.Value.Name);
115+
_logger.LogInformation("Reloading version cache for {project}", ProjectName);
115116

116-
_latestTag = (await _gl.GetLatestReleaseAsync(_cachedProject.Value.Id))?.TagName;
117+
_latestTag = (await _gl.GetLatestReleaseAsync(ProjectId))?.TagName;
117118

118119
if (_latestTag is null)
119120
{
120-
_logger.LogWarning("Latest version for {project} was a 404, aborting.", _cachedProject.Value.Name);
121+
_logger.LogWarning("Latest version for {project} was a 404, aborting.", ProjectName);
121122
return;
122123
}
123124

124125
var sw = Stopwatch.StartNew();
125126

126-
var releases = await _gl.PageReleases(_cachedProject.Value.Id)
127+
var releases = await _gl.PageReleases(ProjectId)
127128
.GetAllAsync(onNonSuccess:
128129
code => _logger.LogError(
129130
"One of the pagination requests to get all releases returned a non-success status code: {code}",
@@ -178,13 +179,13 @@ public async Task RefreshAsync()
178179
if (Count > 0)
179180
{
180181
_logger.LogInformation("Clearing {entryCount} version cache entries for {project}", Count,
181-
_cachedProject!.Value.Name);
182+
ProjectName);
182183
Clear();
183184
}
184185

185186
foreach (var (tag, entry) in tempCacheEntries)
186187
{
187-
_logger.LogTrace("Adding version cache entry {tag} for {project}", tag, _cachedProject!.Value.Name);
188+
_logger.LogTrace("Adding version cache entry {tag} for {project}", tag, ProjectName);
188189

189190
this[tag] = entry;
190191
}
@@ -196,7 +197,7 @@ public async Task RefreshAsync()
196197
_semaphore.Release();
197198

198199
_logger.LogInformation("Loaded {entryCount} version cache entries for {project}; took {time}ms.", Count,
199-
_cachedProject!.Value.Name, sw.ElapsedMilliseconds);
200+
ProjectName, sw.ElapsedMilliseconds);
200201
}
201202

202203
public static void InitializeVersionCaches(WebApplication app)

0 commit comments

Comments
 (0)