Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 68 additions & 25 deletions src/Server/Controllers/Api/v1/VersionController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.ComponentModel;
using Gommon;
using Microsoft.AspNetCore.Mvc;
using Ryujinx.Systems.Update.Common;
using Ryujinx.Systems.Update.Server.Helpers.Results;
using Ryujinx.Systems.Update.Server.Services.Forgejo;

namespace Ryujinx.Systems.Update.Server.Controllers;
Expand All @@ -25,25 +28,39 @@ public async Task<ActionResult<VersionResponse>> GetLatestStable(
if (!arch.TryParseAsSupportedArchitecture(out var supportedArch))
return BadRequest($"Unknown architecture '{arch}'");

if (await vcache.GetReleaseAsync(c => c.GetLatest(supportedPlatform, supportedArch)) is not { } latest)
return NotFound();
Return<VersionCacheEntry?> result = await vcache.GetReleaseAsync(c => c.GetLatest(supportedPlatform, supportedArch));

if (result.IsOf(out ReleaseError error))
{
return error.Error switch
{
ReleaseError.ReleaseErrorType.NotInitialized => NotFound(),
ReleaseError.ReleaseErrorType.RateLimited => Problem("The version cache has hit the request rate limit.", statusCode: StatusCodes.Status429TooManyRequests),
_ => throw new InvalidEnumArgumentException()
};
}

if (result.IsSuccess && result.Unwrap() is { } latest)
{
if (!Config.EnabledEndpoints.LatestQuery)
return Ok(new VersionResponse
{
Version = latest.Tag,
ArtifactUrl = "",
MaxConcurrency = Config.MaxConcurrentDownloads,
ReleaseUrlFormat = vcache.ReleaseUrlFormat
});

if (!Config.EnabledEndpoints.LatestQuery)
return Ok(new VersionResponse
{
Version = latest.Tag,
ArtifactUrl = "",
ArtifactUrl = latest.GetUrlFor(supportedPlatform, supportedArch),
MaxConcurrency = Config.MaxConcurrentDownloads,
ReleaseUrlFormat = vcache.ReleaseUrlFormat
});

return Ok(new VersionResponse
{
Version = latest.Tag,
ArtifactUrl = latest.GetUrlFor(supportedPlatform, supportedArch),
MaxConcurrency = Config.MaxConcurrentDownloads,
ReleaseUrlFormat = vcache.ReleaseUrlFormat
});
}

return NotFound();
}

[HttpGet($"{Constants.CanaryRoute}/{Constants.RouteName_Latest}")]
Expand All @@ -62,26 +79,40 @@ public async Task<ActionResult<VersionResponse>> GetLatestCanary(

if (!arch.TryParseAsSupportedArchitecture(out var supportedArch))
return BadRequest($"Unknown architecture '{arch}'");

Return<VersionCacheEntry?> result = await vcache.GetReleaseAsync(c => c.GetLatest(supportedPlatform, supportedArch));

if (result.IsOf(out ReleaseError error))
{
return error.Error switch
{
ReleaseError.ReleaseErrorType.NotInitialized => NotFound(),
ReleaseError.ReleaseErrorType.RateLimited => Problem("The version cache has hit the request rate limit.", statusCode: StatusCodes.Status429TooManyRequests),
_ => throw new InvalidEnumArgumentException()
};
}

if (await vcache.GetReleaseAsync(c => c.GetLatest(supportedPlatform, supportedArch)) is not { } latest)
return NotFound();
if (result.IsSuccess && result.Unwrap() is { } latest)
{
if (!Config.EnabledEndpoints.LatestQuery)
return Ok(new VersionResponse
{
Version = latest.Tag,
ArtifactUrl = "",
MaxConcurrency = Config.MaxConcurrentDownloads,
ReleaseUrlFormat = vcache.ReleaseUrlFormat
});

if (!Config.EnabledEndpoints.LatestQuery)
return Ok(new VersionResponse
{
Version = latest.Tag,
ArtifactUrl = "",
ArtifactUrl = latest.GetUrlFor(supportedPlatform, supportedArch),
MaxConcurrency = Config.MaxConcurrentDownloads,
ReleaseUrlFormat = vcache.ReleaseUrlFormat
});

return Ok(new VersionResponse
{
Version = latest.Tag,
ArtifactUrl = latest.GetUrlFor(supportedPlatform, supportedArch),
MaxConcurrency = Config.MaxConcurrentDownloads,
ReleaseUrlFormat = vcache.ReleaseUrlFormat
});
}

return NotFound();
}

[HttpGet($"{Constants.StableRoute}/{{version}}")]
Expand Down Expand Up @@ -110,7 +141,19 @@ public async Task<ActionResult<VersionCacheEntry>> GetSpecificCanary(
string version
)
{
if (await vcache.GetReleaseAsync(c => c[version]) is { } cacheEntry)
Return<VersionCacheEntry?> result = await vcache.GetReleaseAsync(c => c[version]);

if (result.IsOf(out ReleaseError error))
{
return error.Error switch
{
ReleaseError.ReleaseErrorType.NotInitialized => NotFound(),
ReleaseError.ReleaseErrorType.RateLimited => Problem("The version cache has hit the request rate limit.", statusCode: StatusCodes.Status429TooManyRequests),
_ => throw new InvalidEnumArgumentException()
};
}

if (result.IsSuccess && result.Unwrap() is {} cacheEntry)
return Ok(cacheEntry);

return NotFound();
Expand Down
57 changes: 47 additions & 10 deletions src/Server/Controllers/DownloadController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.ComponentModel;
using Gommon;
using Microsoft.AspNetCore.Mvc;
using Ryujinx.Systems.Update.Common;
using Ryujinx.Systems.Update.Server.Helpers.Results;
using Ryujinx.Systems.Update.Server.Services.Forgejo;

namespace Ryujinx.Systems.Update.Server.Controllers;
Expand Down Expand Up @@ -43,16 +46,26 @@ public async Task<ActionResult> DownloadCustom(
return BadRequest(
$"Unknown release channel '{rc}'; valid are '{Constants.StableRoute}' and '{Constants.CanaryRoute}'");

var release = await HttpContext.RequestServices
var result = await HttpContext.RequestServices
.GetCacheFor(releaseChannel)
.GetReleaseAsync(c =>
version is Constants.RouteName_Latest ? c.GetLatest(supportedPlatform, supportedArch) : c[version]
);

if (release is null)
return NotFound();
if (result.IsOf(out ReleaseError error))
{
return error.Error switch
{
ReleaseError.ReleaseErrorType.NotInitialized => NotFound(),
ReleaseError.ReleaseErrorType.RateLimited => Problem("The version cache has hit the request rate limit.", statusCode: StatusCodes.Status429TooManyRequests),
_ => throw new InvalidEnumArgumentException()
};
}

if (result.IsSuccess && result.Unwrap() is {} release)
return Redirect(release.GetUrlFor(supportedPlatform, supportedArch));

return Redirect(release.GetUrlFor(supportedPlatform, supportedArch));
return NotFound();
}

[HttpGet]
Expand All @@ -69,10 +82,22 @@ [FromServices] ILogger<DownloadController> logger
return Problem("This instance of Ryubing UpdateServer is not configured to support this endpoint.",
statusCode: 418);

if (await vcache.GetReleaseAsync(c => c.Latest) is not { } latest)
return NotFound();
Return<VersionCacheEntry?> result = await vcache.GetReleaseAsync(c => c.Latest);

return RedirectOrProblem(latest, logger, HttpContext.Request.Headers.UserAgent.ToString());
if (result.IsOf(out ReleaseError error))
{
return error.Error switch
{
ReleaseError.ReleaseErrorType.NotInitialized => NotFound(),
ReleaseError.ReleaseErrorType.RateLimited => Problem("The version cache has hit the request rate limit.", statusCode: StatusCodes.Status429TooManyRequests),
_ => throw new InvalidEnumArgumentException()
};
}

if (result.IsSuccess && result.Unwrap() is {} latest)
return RedirectOrProblem(latest, logger, HttpContext.Request.Headers.UserAgent.ToString());

return NotFound();
}

[HttpGet(Constants.CanaryRoute)]
Expand All @@ -88,10 +113,22 @@ [FromServices] ILogger<DownloadController> logger
return Problem("This instance of Ryubing UpdateServer is not configured to support this endpoint.",
statusCode: 418);

if (await vcache.GetReleaseAsync(c => c.Latest) is not { } latest)
return NotFound();
Return<VersionCacheEntry?> result = await vcache.GetReleaseAsync(c => c.Latest);

return RedirectOrProblem(latest, logger, HttpContext.Request.Headers.UserAgent.ToString());
if (result.IsOf(out ReleaseError error))
{
return error.Error switch
{
ReleaseError.ReleaseErrorType.NotInitialized => NotFound(),
ReleaseError.ReleaseErrorType.RateLimited => Problem("The version cache has hit the request rate limit.", statusCode: StatusCodes.Status429TooManyRequests),
_ => throw new InvalidEnumArgumentException()
};
}

if (result.IsSuccess && result.Unwrap() is {} latest)
return RedirectOrProblem(latest, logger, HttpContext.Request.Headers.UserAgent.ToString());

return NotFound();
}

private ActionResult RedirectOrProblem(VersionCacheEntry cacheEntry, ILogger<DownloadController> logger,
Expand Down
74 changes: 58 additions & 16 deletions src/Server/Controllers/LatestController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel;
using Gommon;
using Microsoft.AspNetCore.Mvc;
using Ryujinx.Systems.Update.Common;
using Ryujinx.Systems.Update.Server.Helpers.Results;
using Ryujinx.Systems.Update.Server.Services.Forgejo;

namespace Ryujinx.Systems.Update.Server.Controllers;
Expand Down Expand Up @@ -40,37 +43,64 @@ public async Task<ActionResult<VersionResponse>> GetLatestCustom(

var vcache = HttpContext.RequestServices.GetCacheFor(releaseChannel);

if (await vcache.GetReleaseAsync(c => c.GetLatest(supportedPlatform, supportedArch)) is not { } latest)
return NotFound();
Return<VersionCacheEntry?> result = await vcache.GetReleaseAsync(c => c.GetLatest(supportedPlatform, supportedArch));

if (!Config.EnabledEndpoints.LatestQuery)
if (result.IsOf(out ReleaseError error))
{
return error.Error switch
{
ReleaseError.ReleaseErrorType.NotInitialized => NotFound(),
ReleaseError.ReleaseErrorType.RateLimited => Problem("The version cache has hit the request rate limit.", statusCode: StatusCodes.Status429TooManyRequests),
_ => throw new InvalidEnumArgumentException()
};
}

if (result.IsSuccess && result.Unwrap() is { } latest)
{
if (!Config.EnabledEndpoints.LatestQuery)
return Ok(new VersionResponse
{
Version = latest.Tag,
ArtifactUrl = "",
MaxConcurrency = Config.MaxConcurrentDownloads,
ReleaseUrlFormat = vcache.ReleaseUrlFormat
});

return Ok(new VersionResponse
{
Version = latest.Tag,
ArtifactUrl = "",
ArtifactUrl = latest.GetUrlFor(supportedPlatform, supportedArch),
MaxConcurrency = Config.MaxConcurrentDownloads,
ReleaseUrlFormat = vcache.ReleaseUrlFormat
});

return Ok(new VersionResponse
{
Version = latest.Tag,
ArtifactUrl = latest.GetUrlFor(supportedPlatform, supportedArch),
MaxConcurrency = Config.MaxConcurrentDownloads,
ReleaseUrlFormat = vcache.ReleaseUrlFormat
});
}

return NotFound();
}

[HttpGet(Constants.StableRoute), HttpGet]
[ProducesResponseType(StatusCodes.Status302Found)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status429TooManyRequests)]
[EndpointDescription("Redirect to the Forgejo release URL of the latest Stable Ryubing release.")]
public async Task<ActionResult> RedirectLatestStable(
[FromKeyedServices("stableCache")] ForgejoVersionCache vcache)
{
if (await vcache.GetReleaseAsync(c => c.Latest) is { } latest)
return Redirect(latest.ReleaseUrl);
Return<VersionCacheEntry?> result = await vcache.GetReleaseAsync(c => c.Latest);

if (result.IsOf(out ReleaseError error))
{
return error.Error switch
{
ReleaseError.ReleaseErrorType.NotInitialized => NotFound(),
ReleaseError.ReleaseErrorType.RateLimited => Problem("The version cache has hit the request rate limit.", statusCode: StatusCodes.Status429TooManyRequests),
_ => throw new InvalidEnumArgumentException()
};
}

if (result.IsSuccess && result.Unwrap() is {} latest)
return Redirect(latest.ReleaseUrl);

return NotFound();
}

Expand All @@ -81,7 +111,19 @@ public async Task<ActionResult> RedirectLatestStable(
public async Task<ActionResult> RedirectLatestCanary(
[FromKeyedServices("canaryCache")] ForgejoVersionCache vcache)
{
if (await vcache.GetReleaseAsync(c => c.Latest) is { } latest)
Return<VersionCacheEntry?> result = await vcache.GetReleaseAsync(c => c.Latest);

if (result.IsOf(out ReleaseError error))
{
return error.Error switch
{
ReleaseError.ReleaseErrorType.NotInitialized => NotFound(),
ReleaseError.ReleaseErrorType.RateLimited => Problem("The version cache has hit the request rate limit.", statusCode: StatusCodes.Status429TooManyRequests),
_ => throw new InvalidEnumArgumentException()
};
}

if (result.IsSuccess && result.Unwrap() is {} latest)
return Redirect(latest.ReleaseUrl);

return NotFound();
Expand Down
16 changes: 16 additions & 0 deletions src/Server/Helpers/Results/ReleaseError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Gommon;

namespace Ryujinx.Systems.Update.Server.Helpers.Results;

public readonly struct ReleaseError(ReleaseError.ReleaseErrorType error) : IErrorState
{
public ReleaseErrorType Error { get; } = error;

public override string ToString() => Error.ToString();

public enum ReleaseErrorType
{
NotInitialized,
RateLimited,
}
}
Loading