using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using RadicadeServer.API.Studio;
namespace RadicadeServer.CDN
{
///
/// Step one of the client's two-step bundle download. The client asks
/// /rooms/unity_assets/{assetId}/{target}/{version} and is handed a file name, then
/// ranges over that file at /data/{Filename} (and, for a subroom's bundle, under the
/// CDN's own /unity/{Filename} alias).
///
/// The id arrives in two shapes and both have to be answered. A Studio manifest id is 40
/// alphanumerics and its baked bundles are named in the manifest's own metadata. A legacy
/// bundle is stored under its file name (or that name minus .assetbundle), and that
/// name is what the subroom's UnityAssetId carries - every imported room, the built-in
/// rooms and every room bound from the admin panel. Answering only the 40-character shape
/// leaves every legacy bundle unresolvable, and the client reports one 400 as
/// "Error downloading room assets" for the whole room.
///
[Route("rooms/")]
public class UnityAssetsCDN : Controller
{
[HttpGet("unity_assets/{assetId}/{target}/{version}")]
public async Task GetUnityAsset(string assetId, int target, int version)
{
return await ServeMetadataAsync(assetId, target, version);
}
[HttpGet("unity_assets/{assetId}")]
public async Task GetUnityAsset(string assetId)
{
return await ServeMetadataAsync(assetId, 0, 1);
}
private async Task ServeMetadataAsync(string assetId, int target, int version)
{
if (!CdnStorageNames.IsServable(assetId) || target < 0 || target > byte.MaxValue || version < 0)
return BadRequest();
string? filename = null;
string? rejectedDependencies = null;
if (StudioUnityAssetRegistry.IsValidAssetId(assetId))
{
var asset = await StudioUnityAssetRegistry.ReadAsync(assetId, HttpContext.RequestAborted);
var baked = asset?.BakedUnityAssets.FirstOrDefault(x => x.Target == target && x.Version == version);
if (baked != null)
{
filename = baked.Filename;
rejectedDependencies = baked.RejectedDependenciesFilename;
}
else if (asset != null)
{
// The manifest exists, so the file on disk under this id is that JSON
// manifest and not a bundle. A target/version it does not name is a 404:
// handing the manifest over as the bundle would have the client parse
// metadata as an asset bundle.
return NotFound();
}
}
// Reached for every legacy id, and for a 40-character id with no manifest behind it:
// the bundle is the file stored under the name the subroom points at, with or without
// the .assetbundle suffix an import appends.
filename ??= CdnStorageNames.Resolve(assetId);
if (filename == null) return NotFound();
var body = JsonSerializer.Serialize(new
{
UnityAssetId = assetId,
Target = target,
Version = version,
Filename = filename,
RejectedDependenciesFilename = rejectedDependencies
});
return Content(body, "application/json");
}
}
}