2024-12-27 19:15:33 +03:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2025-08-06 21:29:00 +03:00
|
|
|
using Nebula.Shared.Configurations;
|
2025-01-05 17:05:23 +03:00
|
|
|
using Nebula.Shared.FileApis;
|
2025-05-02 20:06:33 +03:00
|
|
|
using Nebula.Shared.FileApis.Interfaces;
|
2025-01-05 17:05:23 +03:00
|
|
|
using Nebula.Shared.Models;
|
2025-05-05 20:43:28 +03:00
|
|
|
using Nebula.Shared.Services.Logging;
|
2025-01-05 17:05:23 +03:00
|
|
|
using Nebula.Shared.Utils;
|
2024-12-27 19:15:33 +03:00
|
|
|
|
2025-01-05 17:05:23 +03:00
|
|
|
namespace Nebula.Shared.Services;
|
2024-12-27 19:15:33 +03:00
|
|
|
|
|
|
|
|
[ServiceRegister]
|
2025-01-05 17:05:23 +03:00
|
|
|
public sealed class EngineService
|
2024-12-27 19:15:33 +03:00
|
|
|
{
|
|
|
|
|
private readonly AssemblyService _assemblyService;
|
|
|
|
|
private readonly FileService _fileService;
|
|
|
|
|
private readonly RestService _restService;
|
|
|
|
|
private readonly ConfigurationService _varService;
|
2025-01-14 22:10:16 +03:00
|
|
|
private readonly Task _currInfoTask;
|
2025-05-02 20:06:33 +03:00
|
|
|
private readonly IReadWriteFileApi _engineFileApi;
|
2025-05-05 20:43:28 +03:00
|
|
|
private readonly ILogger _logger;
|
2025-05-02 20:06:33 +03:00
|
|
|
|
2025-05-05 20:43:28 +03:00
|
|
|
private ModulesInfo? _modulesInfo;
|
|
|
|
|
private Dictionary<string, EngineVersionInfo>? _versionsInfo;
|
2024-12-27 19:15:33 +03:00
|
|
|
|
|
|
|
|
public EngineService(RestService restService, DebugService debugService, ConfigurationService varService,
|
2025-05-02 20:06:33 +03:00
|
|
|
FileService fileService, AssemblyService assemblyService)
|
2024-12-27 19:15:33 +03:00
|
|
|
{
|
|
|
|
|
_restService = restService;
|
2025-05-05 20:43:28 +03:00
|
|
|
_logger = debugService.GetLogger(this);
|
2024-12-27 19:15:33 +03:00
|
|
|
_varService = varService;
|
|
|
|
|
_fileService = fileService;
|
|
|
|
|
_assemblyService = assemblyService;
|
|
|
|
|
|
2025-05-02 20:06:33 +03:00
|
|
|
_engineFileApi = fileService.CreateFileApi("engine");
|
2025-01-05 17:05:23 +03:00
|
|
|
_currInfoTask = Task.Run(() => LoadEngineManifest(CancellationToken.None));
|
2024-12-27 19:15:33 +03:00
|
|
|
}
|
|
|
|
|
|
2025-05-05 20:43:28 +03:00
|
|
|
public void GetEngineInfo(out ModulesInfo modulesInfo, out Dictionary<string, EngineVersionInfo> versionsInfo)
|
|
|
|
|
{
|
|
|
|
|
if(!_currInfoTask.IsCompleted) _currInfoTask.Wait();
|
|
|
|
|
if(_currInfoTask.Exception != null) throw new Exception("Error while loading engine manifest:",_currInfoTask.Exception);
|
|
|
|
|
|
|
|
|
|
if(_modulesInfo == null || _versionsInfo == null) throw new NullReferenceException("Engine manifest is null");
|
|
|
|
|
|
|
|
|
|
modulesInfo = _modulesInfo;
|
|
|
|
|
versionsInfo = _versionsInfo;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-27 19:15:33 +03:00
|
|
|
public async Task LoadEngineManifest(CancellationToken cancellationToken)
|
|
|
|
|
{
|
2025-05-05 20:43:28 +03:00
|
|
|
_logger.Log("start fetching engine manifest");
|
2025-05-02 20:06:33 +03:00
|
|
|
_versionsInfo = await LoadExacManifest(CurrentConVar.EngineManifestUrl, CurrentConVar.EngineManifestBackup, cancellationToken);
|
|
|
|
|
_modulesInfo = await LoadExacManifest(CurrentConVar.EngineModuleManifestUrl, CurrentConVar.ModuleManifestBackup, cancellationToken);
|
2025-05-05 20:43:28 +03:00
|
|
|
_logger.Log("fetched engine manifest successfully");
|
2025-05-02 20:06:33 +03:00
|
|
|
}
|
2025-01-29 12:46:23 +03:00
|
|
|
|
2025-05-02 20:06:33 +03:00
|
|
|
private async Task<T> LoadExacManifest<T>(ConVar<string[]> conVar,ConVar<T> backup,CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var manifestUrls = _varService.GetConfigValue(conVar)!;
|
2025-01-29 12:46:23 +03:00
|
|
|
|
2025-05-02 20:06:33 +03:00
|
|
|
foreach (var manifestUrl in manifestUrls)
|
2025-01-29 12:46:23 +03:00
|
|
|
{
|
2025-05-02 20:06:33 +03:00
|
|
|
try
|
|
|
|
|
{
|
2025-05-05 20:43:28 +03:00
|
|
|
_logger.Log("Fetching engine manifest from: " + manifestUrl);
|
2025-05-02 20:06:33 +03:00
|
|
|
var info = await _restService.GetAsync<T>(
|
|
|
|
|
new Uri(manifestUrl), cancellationToken);
|
2025-01-29 12:46:23 +03:00
|
|
|
|
2025-05-02 20:06:33 +03:00
|
|
|
_varService.SetConfigValue(backup, info);
|
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
2025-01-29 12:46:23 +03:00
|
|
|
{
|
2025-05-05 20:43:28 +03:00
|
|
|
_logger.Error($"error while attempt fetch engine manifest: {e.Message}");
|
2025-01-29 12:46:23 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-05 20:43:28 +03:00
|
|
|
_logger.Debug("Trying fallback module manifest...");
|
2025-05-02 20:06:33 +03:00
|
|
|
if (!_varService.TryGetConfigValue(backup, out var moduleInfo))
|
|
|
|
|
{
|
|
|
|
|
throw new Exception("No module info data available");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return moduleInfo;
|
2024-12-27 19:15:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public EngineBuildInfo? GetVersionInfo(string version)
|
|
|
|
|
{
|
2025-05-05 20:43:28 +03:00
|
|
|
GetEngineInfo(out var modulesInfo, out var engineVersionInfo);
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-05-05 20:43:28 +03:00
|
|
|
if (!engineVersionInfo.TryGetValue(version, out var foundVersion))
|
2024-12-27 19:15:33 +03:00
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
if (foundVersion.RedirectVersion != null)
|
|
|
|
|
return GetVersionInfo(foundVersion.RedirectVersion);
|
|
|
|
|
|
|
|
|
|
var bestRid = RidUtility.FindBestRid(foundVersion.Platforms.Keys);
|
|
|
|
|
if (bestRid == null) bestRid = "linux-x64";
|
|
|
|
|
|
2025-05-05 20:43:28 +03:00
|
|
|
_logger.Log("Selecting RID" + bestRid);
|
2024-12-27 19:15:33 +03:00
|
|
|
|
|
|
|
|
return foundVersion.Platforms[bestRid];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool TryGetVersionInfo(string version, [NotNullWhen(true)] out EngineBuildInfo? info)
|
|
|
|
|
{
|
|
|
|
|
info = GetVersionInfo(version);
|
|
|
|
|
return info != null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 23:25:25 +03:00
|
|
|
public async Task<AssemblyApi?> EnsureEngine(string version, ILoadingHandlerFactory loadingHandlerFactory, CancellationToken cancellationToken = default)
|
2024-12-27 19:15:33 +03:00
|
|
|
{
|
2025-05-05 20:43:28 +03:00
|
|
|
_logger.Log("Ensure engine " + version);
|
2025-12-06 23:25:25 +03:00
|
|
|
using var loadingHandler = loadingHandlerFactory.CreateLoadingContext(new FileLoadingFormater());
|
|
|
|
|
loadingHandler.SetLoadingMessage("Ensuring engine " + version);
|
2024-12-27 19:15:33 +03:00
|
|
|
|
2025-12-06 23:25:25 +03:00
|
|
|
if (!TryOpen(version)) await DownloadEngine(version, loadingHandler, cancellationToken);
|
2024-12-27 19:15:33 +03:00
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-05-02 20:06:33 +03:00
|
|
|
var api = _fileService.OpenZip(version, _engineFileApi);
|
2025-01-08 18:00:06 +03:00
|
|
|
if (api != null) return _assemblyService.Mount(api);
|
2024-12-27 19:15:33 +03:00
|
|
|
}
|
2025-01-19 22:52:29 +03:00
|
|
|
catch (Exception)
|
2024-12-27 19:15:33 +03:00
|
|
|
{
|
2025-05-02 20:06:33 +03:00
|
|
|
_engineFileApi.Remove(version);
|
2024-12-27 19:15:33 +03:00
|
|
|
throw;
|
|
|
|
|
}
|
2025-01-08 18:00:06 +03:00
|
|
|
|
|
|
|
|
return null;
|
2024-12-27 19:15:33 +03:00
|
|
|
}
|
|
|
|
|
|
2025-12-06 23:25:25 +03:00
|
|
|
public async Task DownloadEngine(string version, ILoadingHandler loadingHandler, CancellationToken cancellationToken = default)
|
2024-12-27 19:15:33 +03:00
|
|
|
{
|
|
|
|
|
if (!TryGetVersionInfo(version, out var info))
|
|
|
|
|
return;
|
|
|
|
|
|
2025-05-05 20:43:28 +03:00
|
|
|
_logger.Log("Downloading engine version " + version);
|
2025-12-06 23:25:25 +03:00
|
|
|
loadingHandler.SetLoadingMessage("Downloading engine version " + version);
|
|
|
|
|
loadingHandler.Clear();
|
|
|
|
|
|
2024-12-27 19:15:33 +03:00
|
|
|
using var client = new HttpClient();
|
2025-12-06 23:25:25 +03:00
|
|
|
|
|
|
|
|
var response = await client.GetAsync(info.Url, cancellationToken);
|
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
|
loadingHandler.SetJobsCount(response.Content.Headers.ContentLength ?? 0);
|
|
|
|
|
|
|
|
|
|
await using var streamContent = await response.Content.ReadAsStreamAsync(cancellationToken);
|
|
|
|
|
var s = await client.GetStreamAsync(info.Url, cancellationToken);
|
|
|
|
|
_engineFileApi.Save(version, s, loadingHandler);
|
2024-12-27 19:15:33 +03:00
|
|
|
await s.DisposeAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool TryOpen(string version, [NotNullWhen(true)] out Stream? stream)
|
|
|
|
|
{
|
2025-05-02 20:06:33 +03:00
|
|
|
return _engineFileApi.TryOpen(version, out stream);
|
2024-12-27 19:15:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool TryOpen(string version)
|
|
|
|
|
{
|
|
|
|
|
var a = TryOpen(version, out var stream);
|
|
|
|
|
if (a) stream!.Close();
|
|
|
|
|
return a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public EngineBuildInfo? GetModuleBuildInfo(string moduleName, string version)
|
|
|
|
|
{
|
2025-05-05 20:43:28 +03:00
|
|
|
GetEngineInfo(out var modulesInfo, out var engineVersionInfo);
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-05-05 20:43:28 +03:00
|
|
|
if (!modulesInfo.Modules.TryGetValue(moduleName, out var module) ||
|
2024-12-27 19:15:33 +03:00
|
|
|
!module.Versions.TryGetValue(version, out var value))
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
var bestRid = RidUtility.FindBestRid(value.Platforms.Keys);
|
|
|
|
|
if (bestRid == null) throw new Exception("No engine version available for our platform!");
|
|
|
|
|
|
|
|
|
|
return value.Platforms[bestRid];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool TryGetModuleBuildInfo(string moduleName, string version, [NotNullWhen(true)] out EngineBuildInfo? info)
|
|
|
|
|
{
|
|
|
|
|
info = GetModuleBuildInfo(moduleName, version);
|
|
|
|
|
return info != null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string ResolveModuleVersion(string moduleName, string engineVersion)
|
|
|
|
|
{
|
2025-05-05 20:43:28 +03:00
|
|
|
GetEngineInfo(out var modulesInfo, out var engineVersionInfo);
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-12-06 23:25:25 +03:00
|
|
|
var engineVersionObj = Version.Parse(engineVersion.Split("-")[0]);
|
2025-05-05 20:43:28 +03:00
|
|
|
var module = modulesInfo.Modules[moduleName];
|
2025-02-01 18:19:18 +03:00
|
|
|
var selectedVersion = module.Versions.Select(kv => new { Version = Version.Parse(kv.Key), kv.Key, kv })
|
2024-12-27 19:15:33 +03:00
|
|
|
.Where(kv => engineVersionObj >= kv.Version)
|
|
|
|
|
.MaxBy(kv => kv.Version);
|
|
|
|
|
|
|
|
|
|
if (selectedVersion == null) throw new Exception();
|
|
|
|
|
|
|
|
|
|
return selectedVersion.Key;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 23:25:25 +03:00
|
|
|
public async Task<AssemblyApi?> EnsureEngineModules(string moduleName, ILoadingHandlerFactory loadingHandlerFactory, string engineVersion)
|
2024-12-27 19:15:33 +03:00
|
|
|
{
|
|
|
|
|
var moduleVersion = ResolveModuleVersion(moduleName, engineVersion);
|
|
|
|
|
if (!TryGetModuleBuildInfo(moduleName, moduleVersion, out var buildInfo))
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
var fileName = ConcatName(moduleName, moduleVersion);
|
2025-12-06 23:25:25 +03:00
|
|
|
|
|
|
|
|
using var loadingHandler = loadingHandlerFactory.CreateLoadingContext(new FileLoadingFormater());
|
|
|
|
|
loadingHandler.SetLoadingMessage("Ensuring engine module " + fileName);
|
2024-12-27 19:15:33 +03:00
|
|
|
|
2025-12-06 23:25:25 +03:00
|
|
|
if (!TryOpen(fileName)) await DownloadEngineModule(moduleName, loadingHandler, moduleVersion);
|
2024-12-27 19:15:33 +03:00
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-05-05 20:43:28 +03:00
|
|
|
return _assemblyService.Mount(
|
|
|
|
|
_fileService.OpenZip(fileName, _engineFileApi) ??
|
|
|
|
|
throw new InvalidOperationException($"{fileName} is not exist!"));
|
2024-12-27 19:15:33 +03:00
|
|
|
}
|
2025-01-19 22:52:29 +03:00
|
|
|
catch (Exception)
|
2024-12-27 19:15:33 +03:00
|
|
|
{
|
2025-05-02 20:06:33 +03:00
|
|
|
_engineFileApi.Remove(fileName);
|
2024-12-27 19:15:33 +03:00
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 23:25:25 +03:00
|
|
|
public async Task DownloadEngineModule(string moduleName, ILoadingHandler loadingHandler, string moduleVersion)
|
2024-12-27 19:15:33 +03:00
|
|
|
{
|
|
|
|
|
if (!TryGetModuleBuildInfo(moduleName, moduleVersion, out var info))
|
|
|
|
|
return;
|
|
|
|
|
|
2025-05-05 20:43:28 +03:00
|
|
|
_logger.Log("Downloading engine module version " + moduleVersion);
|
2025-12-06 23:25:25 +03:00
|
|
|
loadingHandler.SetLoadingMessage("Downloading engine module version " + moduleVersion);
|
2024-12-27 19:15:33 +03:00
|
|
|
using var client = new HttpClient();
|
|
|
|
|
var s = await client.GetStreamAsync(info.Url);
|
2025-12-06 23:25:25 +03:00
|
|
|
_engineFileApi.Save(ConcatName(moduleName, moduleVersion), s, loadingHandler);
|
2024-12-27 19:15:33 +03:00
|
|
|
await s.DisposeAsync();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 23:25:25 +03:00
|
|
|
private string ConcatName(string moduleName, string moduleVersion)
|
2024-12-27 19:15:33 +03:00
|
|
|
{
|
|
|
|
|
return moduleName + "" + moduleVersion;
|
|
|
|
|
}
|
|
|
|
|
}
|