using Microsoft.AspNetCore.Mvc; using System.Security.Cryptography; namespace RR3CommunityServer.Controllers; [ApiController] [Route("synergy/content")] public class ContentController : ControllerBase { private readonly IWebHostEnvironment _env; private readonly string _assetsPath; public ContentController(IWebHostEnvironment env) { _env = env; _assetsPath = Path.Combine(_env.ContentRootPath, "Assets"); // Create assets directory structure if not exists Directory.CreateDirectory(Path.Combine(_assetsPath, "cars")); Directory.CreateDirectory(Path.Combine(_assetsPath, "tracks")); Directory.CreateDirectory(Path.Combine(_assetsPath, "textures")); Directory.CreateDirectory(Path.Combine(_assetsPath, "audio")); } /// /// Get asset manifest - list of all available content /// [HttpGet("manifest")] public IActionResult GetManifest() { var assets = GetAssetList(); return Ok(new { version = "1.0.0", assetCount = assets.Count, totalSize = assets.Sum(a => ((dynamic)a).size), assets = assets }); } /// /// Download specific asset file /// [HttpGet("download/{assetType}/{assetId}")] public IActionResult DownloadAsset(string assetType, string assetId) { var assetPath = Path.Combine(_assetsPath, assetType, $"{assetId}.pak"); if (!System.IO.File.Exists(assetPath)) { return NotFound(new { error = $"Asset not found: {assetType}/{assetId}" }); } var fileBytes = System.IO.File.ReadAllBytes(assetPath); var fileName = $"{assetId}.pak"; return File(fileBytes, "application/octet-stream", fileName); } /// /// Get asset metadata and info /// [HttpGet("info/{assetType}/{assetId}")] public IActionResult GetAssetInfo(string assetType, string assetId) { var assetPath = Path.Combine(_assetsPath, assetType, $"{assetId}.pak"); if (!System.IO.File.Exists(assetPath)) { return NotFound(new { error = $"Asset not found: {assetType}/{assetId}" }); } var fileInfo = new FileInfo(assetPath); return Ok(new { assetId = assetId, assetType = assetType, size = fileInfo.Length, sizeFormatted = FormatBytes(fileInfo.Length), checksum = CalculateMD5(assetPath), version = "1.0.0", downloadUrl = $"/synergy/content/download/{assetType}/{assetId}", lastModified = fileInfo.LastWriteTimeUtc }); } /// /// Check if specific asset exists /// [HttpHead("download/{assetType}/{assetId}")] public IActionResult CheckAssetExists(string assetType, string assetId) { var assetPath = Path.Combine(_assetsPath, assetType, $"{assetId}.pak"); if (System.IO.File.Exists(assetPath)) { var fileInfo = new FileInfo(assetPath); Response.Headers["Content-Length"] = fileInfo.Length.ToString(); Response.Headers["X-Asset-Checksum"] = CalculateMD5(assetPath); return Ok(); } return NotFound(); } /// /// Get assets by type /// [HttpGet("list/{assetType}")] public IActionResult GetAssetsByType(string assetType) { var typePath = Path.Combine(_assetsPath, assetType); if (!Directory.Exists(typePath)) { return Ok(new { assetType = assetType, assets = new List() }); } var assets = new List(); foreach (var file in Directory.GetFiles(typePath, "*.pak")) { var assetId = Path.GetFileNameWithoutExtension(file); var fileInfo = new FileInfo(file); assets.Add(new { id = assetId, type = assetType, size = fileInfo.Length, sizeFormatted = FormatBytes(fileInfo.Length), url = $"/synergy/content/download/{assetType}/{assetId}", checksum = CalculateMD5(file) }); } return Ok(new { assetType = assetType, count = assets.Count, totalSize = assets.Sum(a => ((dynamic)a).size), assets = assets }); } /// /// Health check for content service /// [HttpGet("health")] public IActionResult HealthCheck() { var assetTypes = new[] { "cars", "tracks", "textures", "audio" }; var stats = new Dictionary(); foreach (var type in assetTypes) { var typePath = Path.Combine(_assetsPath, type); if (Directory.Exists(typePath)) { var files = Directory.GetFiles(typePath, "*.pak"); var totalSize = files.Sum(f => new FileInfo(f).Length); stats[type] = new { count = files.Length, size = totalSize, sizeFormatted = FormatBytes(totalSize) }; } else { stats[type] = new { count = 0, size = 0, sizeFormatted = "0 B" }; } } return Ok(new { status = "healthy", assetsPath = _assetsPath, assetTypes = stats }); } private List GetAssetList() { var assets = new List(); if (Directory.Exists(_assetsPath)) { foreach (var typeDir in Directory.GetDirectories(_assetsPath)) { var assetType = Path.GetFileName(typeDir); foreach (var file in Directory.GetFiles(typeDir, "*.pak")) { var assetId = Path.GetFileNameWithoutExtension(file); var fileInfo = new FileInfo(file); assets.Add(new { id = assetId, type = assetType, size = fileInfo.Length, sizeFormatted = FormatBytes(fileInfo.Length), url = $"/synergy/content/download/{assetType}/{assetId}", checksum = CalculateMD5(file) }); } } } return assets; } private string CalculateMD5(string filePath) { using var md5 = MD5.Create(); using var stream = System.IO.File.OpenRead(filePath); var hash = md5.ComputeHash(stream); return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } private string FormatBytes(long bytes) { string[] sizes = { "B", "KB", "MB", "GB", "TB" }; double len = bytes; int order = 0; while (len >= 1024 && order < sizes.Length - 1) { order++; len = len / 1024; } return $"{len:0.##} {sizes[order]}"; } }