- tweak: rework FileApi for services
- tweak: rework filter think - add: content view button - fix: little fixes in services
This commit is contained in:
@@ -5,11 +5,11 @@ namespace Nebula.Shared;
|
||||
|
||||
public static class CurrentConVar
|
||||
{
|
||||
public static readonly ConVar<string> EngineManifestUrl =
|
||||
ConVarBuilder.Build("engine.manifestUrl", "https://robust-builds.cdn.spacestation14.com/manifest.json");
|
||||
public static readonly ConVar<string[]> EngineManifestUrl =
|
||||
ConVarBuilder.Build<string[]>("engine.manifestUrl", ["https://robust-builds.cdn.spacestation14.com/manifest.json"]);
|
||||
|
||||
public static readonly ConVar<string> EngineModuleManifestUrl =
|
||||
ConVarBuilder.Build("engine.moduleManifestUrl", "https://robust-builds.cdn.spacestation14.com/modules.json");
|
||||
public static readonly ConVar<string[]> EngineModuleManifestUrl =
|
||||
ConVarBuilder.Build<string[]>("engine.moduleManifestUrl", ["https://robust-builds.cdn.spacestation14.com/modules.json"]);
|
||||
|
||||
public static readonly ConVar<int> ManifestDownloadProtocolVersion =
|
||||
ConVarBuilder.Build("engine.manifestDownloadProtocolVersion", 1);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Nebula.Shared.FileApis.Interfaces;
|
||||
using Robust.LoaderApi;
|
||||
|
||||
namespace Nebula.Shared.Services;
|
||||
|
||||
@@ -31,21 +33,24 @@ public static class ConVarBuilder
|
||||
public class ConfigurationService
|
||||
{
|
||||
private readonly DebugService _debugService;
|
||||
private readonly FileService _fileService;
|
||||
|
||||
public IReadWriteFileApi ConfigurationApi { get; init; }
|
||||
|
||||
public ConfigurationService(FileService fileService, DebugService debugService)
|
||||
{
|
||||
_fileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
|
||||
_debugService = debugService ?? throw new ArgumentNullException(nameof(debugService));
|
||||
|
||||
ConfigurationApi = fileService.CreateFileApi("config");
|
||||
}
|
||||
|
||||
|
||||
public T? GetConfigValue<T>(ConVar<T> conVar)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(conVar);
|
||||
|
||||
try
|
||||
{
|
||||
if (_fileService.ConfigurationApi.TryOpen(GetFileName(conVar), out var stream))
|
||||
if (ConfigurationApi.TryOpen(GetFileName(conVar), out var stream))
|
||||
using (stream)
|
||||
{
|
||||
var obj = JsonSerializer.Deserialize<T>(stream);
|
||||
@@ -72,7 +77,7 @@ public class ConfigurationService
|
||||
value = default;
|
||||
try
|
||||
{
|
||||
if (_fileService.ConfigurationApi.TryOpen(GetFileName(conVar), out var stream))
|
||||
if (ConfigurationApi.TryOpen(GetFileName(conVar), out var stream))
|
||||
using (stream)
|
||||
{
|
||||
var obj = JsonSerializer.Deserialize<T>(stream);
|
||||
@@ -116,7 +121,7 @@ public class ConfigurationService
|
||||
writer.Flush();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
_fileService.ConfigurationApi.Save(GetFileName(conVar), stream);
|
||||
ConfigurationApi.Save(GetFileName(conVar), stream);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -11,9 +11,12 @@ namespace Nebula.Shared.Services;
|
||||
|
||||
public partial class ContentService
|
||||
{
|
||||
public readonly IReadWriteFileApi ContentFileApi = fileService.CreateFileApi("content");
|
||||
public readonly IReadWriteFileApi ManifestFileApi = fileService.CreateFileApi("manifest");
|
||||
|
||||
public bool CheckManifestExist(RobustManifestItem item)
|
||||
{
|
||||
return fileService.ContentFileApi.Has(item.Hash);
|
||||
return ContentFileApi.Has(item.Hash);
|
||||
}
|
||||
|
||||
public async Task<HashApi> EnsureItems(ManifestReader manifestReader, Uri downloadUri,
|
||||
@@ -31,7 +34,7 @@ public partial class ContentService
|
||||
allItems.Add(item.Value);
|
||||
}
|
||||
|
||||
var hashApi = new HashApi(allItems, fileService.ContentFileApi);
|
||||
var hashApi = new HashApi(allItems, ContentFileApi);
|
||||
|
||||
items = allItems.Where(a=> !hashApi.Has(a)).ToList();
|
||||
|
||||
@@ -46,7 +49,7 @@ public partial class ContentService
|
||||
{
|
||||
debugService.Log("Getting manifest: " + info.Hash);
|
||||
|
||||
if (fileService.ManifestFileApi.TryOpen(info.Hash, out var stream))
|
||||
if (ManifestFileApi.TryOpen(info.Hash, out var stream))
|
||||
{
|
||||
debugService.Log("Loading manifest from: " + info.Hash);
|
||||
return await EnsureItems(new ManifestReader(stream), info.DownloadUri, loadingHandler, cancellationToken);
|
||||
@@ -58,7 +61,7 @@ public partial class ContentService
|
||||
if (!response.IsSuccessStatusCode) throw new Exception();
|
||||
|
||||
await using var streamContent = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
fileService.ManifestFileApi.Save(info.Hash, streamContent);
|
||||
ManifestFileApi.Save(info.Hash, streamContent);
|
||||
streamContent.Seek(0, SeekOrigin.Begin);
|
||||
using var manifestReader = new ManifestReader(streamContent);
|
||||
return await EnsureItems(manifestReader, info.DownloadUri, loadingHandler, cancellationToken);
|
||||
@@ -90,8 +93,6 @@ public partial class ContentService
|
||||
|
||||
loadingHandler.AppendResolvedJob();
|
||||
});
|
||||
|
||||
|
||||
|
||||
if (loadingHandler is IDisposable disposable)
|
||||
{
|
||||
|
||||
45
Nebula.Shared/Services/ContentService.Migration.cs
Normal file
45
Nebula.Shared/Services/ContentService.Migration.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Nebula.Shared.FileApis;
|
||||
using Nebula.Shared.Models;
|
||||
|
||||
namespace Nebula.Shared.Services;
|
||||
|
||||
public partial class ContentService
|
||||
{
|
||||
public bool CheckMigration(ILoadingHandler loadingHandler)
|
||||
{
|
||||
debugService.Log("Checking migration...");
|
||||
|
||||
var migrationList = ContentFileApi.AllFiles.Where(f => !f.Contains("\\")).ToList();
|
||||
if(migrationList.Count == 0) return false;
|
||||
|
||||
debugService.Log($"Found {migrationList.Count} migration files. Starting migration...");
|
||||
Task.Run(() => DoMigration(loadingHandler, migrationList));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DoMigration(ILoadingHandler loadingHandler, List<string> migrationList)
|
||||
{
|
||||
loadingHandler.SetJobsCount(migrationList.Count);
|
||||
|
||||
Parallel.ForEach(migrationList, (f,_)=>MigrateFile(f,loadingHandler));
|
||||
|
||||
if (loadingHandler is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void MigrateFile(string file, ILoadingHandler loadingHandler)
|
||||
{
|
||||
if(!ContentFileApi.TryOpen(file, out var stream))
|
||||
{
|
||||
loadingHandler.AppendResolvedJob();
|
||||
return;
|
||||
}
|
||||
|
||||
ContentFileApi.Save(HashApi.GetManifestPath(file), stream);
|
||||
stream.Dispose();
|
||||
ContentFileApi.Remove(file);
|
||||
loadingHandler.AppendResolvedJob();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Nebula.Shared.FileApis;
|
||||
using Nebula.Shared.FileApis.Interfaces;
|
||||
using Nebula.Shared.Models;
|
||||
using Nebula.Shared.Utils;
|
||||
|
||||
@@ -12,80 +13,68 @@ public sealed class EngineService
|
||||
private readonly DebugService _debugService;
|
||||
private readonly FileService _fileService;
|
||||
private readonly RestService _restService;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ConfigurationService _varService;
|
||||
|
||||
private readonly Task _currInfoTask;
|
||||
|
||||
public Dictionary<string, Module> ModuleInfos = default!;
|
||||
public Dictionary<string, EngineVersionInfo> VersionInfos = default!;
|
||||
private readonly IReadWriteFileApi _engineFileApi;
|
||||
|
||||
private ModulesInfo _modulesInfo = default!;
|
||||
private Dictionary<string, EngineVersionInfo> _versionsInfo = default!;
|
||||
|
||||
public EngineService(RestService restService, DebugService debugService, ConfigurationService varService,
|
||||
FileService fileService, IServiceProvider serviceProvider, AssemblyService assemblyService)
|
||||
FileService fileService, AssemblyService assemblyService)
|
||||
{
|
||||
_restService = restService;
|
||||
_debugService = debugService;
|
||||
_varService = varService;
|
||||
_fileService = fileService;
|
||||
_serviceProvider = serviceProvider;
|
||||
_assemblyService = assemblyService;
|
||||
|
||||
_engineFileApi = fileService.CreateFileApi("engine");
|
||||
|
||||
_currInfoTask = Task.Run(() => LoadEngineManifest(CancellationToken.None));
|
||||
}
|
||||
|
||||
public async Task LoadEngineManifest(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
_versionsInfo = await LoadExacManifest(CurrentConVar.EngineManifestUrl, CurrentConVar.EngineManifestBackup, cancellationToken);
|
||||
_modulesInfo = await LoadExacManifest(CurrentConVar.EngineModuleManifestUrl, CurrentConVar.ModuleManifestBackup, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<T> LoadExacManifest<T>(ConVar<string[]> conVar,ConVar<T> backup,CancellationToken cancellationToken)
|
||||
{
|
||||
var manifestUrls = _varService.GetConfigValue(conVar)!;
|
||||
|
||||
foreach (var manifestUrl in manifestUrls)
|
||||
{
|
||||
_debugService.Log("Fetching engine manifest from: " + CurrentConVar.EngineManifestUrl);
|
||||
var info = await _restService.GetAsync<Dictionary<string, EngineVersionInfo>>(
|
||||
new Uri(_varService.GetConfigValue(CurrentConVar.EngineManifestUrl)!), cancellationToken);
|
||||
|
||||
VersionInfos = info;
|
||||
|
||||
_varService.SetConfigValue(CurrentConVar.EngineManifestBackup, info);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_debugService.Debug("Trying fallback engine manifest...");
|
||||
if (!_varService.TryGetConfigValue(CurrentConVar.EngineManifestBackup, out var engineInfo))
|
||||
try
|
||||
{
|
||||
throw new Exception("No engine info data available",e);
|
||||
}
|
||||
|
||||
VersionInfos = engineInfo;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_debugService.Log("Fetching module manifest from: " + CurrentConVar.EngineModuleManifestUrl);
|
||||
var moduleInfo = await _restService.GetAsync<ModulesInfo>(
|
||||
new Uri(_varService.GetConfigValue(CurrentConVar.EngineModuleManifestUrl)!), cancellationToken);
|
||||
|
||||
if (moduleInfo is null)
|
||||
throw new Exception("Module version info is null");
|
||||
_debugService.Log("Fetching engine manifest from: " + manifestUrl);
|
||||
var info = await _restService.GetAsync<T>(
|
||||
new Uri(manifestUrl), cancellationToken);
|
||||
|
||||
ModuleInfos = moduleInfo.Modules;
|
||||
_varService.SetConfigValue(CurrentConVar.ModuleManifestBackup, moduleInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_debugService.Debug("Trying fallback module manifest...");
|
||||
if (!_varService.TryGetConfigValue(CurrentConVar.ModuleManifestBackup, out var modulesInfo))
|
||||
{
|
||||
throw new Exception("No module info data available",e);
|
||||
_varService.SetConfigValue(backup, info);
|
||||
return info;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_debugService.Error($"error while attempt fetch engine manifest: {e.Message}");
|
||||
}
|
||||
|
||||
ModuleInfos = modulesInfo.Modules;
|
||||
}
|
||||
|
||||
_debugService.Debug("Trying fallback module manifest...");
|
||||
if (!_varService.TryGetConfigValue(backup, out var moduleInfo))
|
||||
{
|
||||
throw new Exception("No module info data available");
|
||||
}
|
||||
|
||||
return moduleInfo;
|
||||
}
|
||||
|
||||
public EngineBuildInfo? GetVersionInfo(string version)
|
||||
{
|
||||
CheckAndWaitValidation();
|
||||
|
||||
if (!VersionInfos.TryGetValue(version, out var foundVersion))
|
||||
if (!_versionsInfo.TryGetValue(version, out var foundVersion))
|
||||
return null;
|
||||
|
||||
if (foundVersion.RedirectVersion != null)
|
||||
@@ -113,12 +102,12 @@ public sealed class EngineService
|
||||
|
||||
try
|
||||
{
|
||||
var api = _fileService.OpenZip(version, _fileService.EngineFileApi);
|
||||
var api = _fileService.OpenZip(version, _engineFileApi);
|
||||
if (api != null) return _assemblyService.Mount(api);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_fileService.EngineFileApi.Remove(version);
|
||||
_engineFileApi.Remove(version);
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -133,13 +122,13 @@ public sealed class EngineService
|
||||
_debugService.Log("Downloading engine version " + version);
|
||||
using var client = new HttpClient();
|
||||
var s = await client.GetStreamAsync(info.Url);
|
||||
_fileService.EngineFileApi.Save(version, s);
|
||||
_engineFileApi.Save(version, s);
|
||||
await s.DisposeAsync();
|
||||
}
|
||||
|
||||
public bool TryOpen(string version, [NotNullWhen(true)] out Stream? stream)
|
||||
{
|
||||
return _fileService.EngineFileApi.TryOpen(version, out stream);
|
||||
return _engineFileApi.TryOpen(version, out stream);
|
||||
}
|
||||
|
||||
public bool TryOpen(string version)
|
||||
@@ -153,7 +142,7 @@ public sealed class EngineService
|
||||
{
|
||||
CheckAndWaitValidation();
|
||||
|
||||
if (!ModuleInfos.TryGetValue(moduleName, out var module) ||
|
||||
if (!_modulesInfo.Modules.TryGetValue(moduleName, out var module) ||
|
||||
!module.Versions.TryGetValue(version, out var value))
|
||||
return null;
|
||||
|
||||
@@ -174,7 +163,7 @@ public sealed class EngineService
|
||||
CheckAndWaitValidation();
|
||||
|
||||
var engineVersionObj = Version.Parse(engineVersion);
|
||||
var module = ModuleInfos[moduleName];
|
||||
var module = _modulesInfo.Modules[moduleName];
|
||||
var selectedVersion = module.Versions.Select(kv => new { Version = Version.Parse(kv.Key), kv.Key, kv })
|
||||
.Where(kv => engineVersionObj >= kv.Version)
|
||||
.MaxBy(kv => kv.Version);
|
||||
@@ -196,11 +185,11 @@ public sealed class EngineService
|
||||
|
||||
try
|
||||
{
|
||||
return _assemblyService.Mount(_fileService.OpenZip(fileName, _fileService.EngineFileApi) ?? throw new InvalidOperationException($"{fileName} is not exist!"));
|
||||
return _assemblyService.Mount(_fileService.OpenZip(fileName, _engineFileApi) ?? throw new InvalidOperationException($"{fileName} is not exist!"));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_fileService.EngineFileApi.Remove(fileName);
|
||||
_engineFileApi.Remove(fileName);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -213,7 +202,7 @@ public sealed class EngineService
|
||||
_debugService.Log("Downloading engine module version " + moduleVersion);
|
||||
using var client = new HttpClient();
|
||||
var s = await client.GetStreamAsync(info.Url);
|
||||
_fileService.EngineFileApi.Save(ConcatName(moduleName, moduleVersion), s);
|
||||
_engineFileApi.Save(ConcatName(moduleName, moduleVersion), s);
|
||||
await s.DisposeAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,62 +14,15 @@ public class FileService
|
||||
Environment.SpecialFolder.ApplicationData), "Datum");
|
||||
|
||||
private readonly DebugService _debugService;
|
||||
|
||||
public readonly IReadWriteFileApi ConfigurationApi;
|
||||
public readonly IReadWriteFileApi ContentFileApi;
|
||||
public readonly IReadWriteFileApi EngineFileApi;
|
||||
public readonly IReadWriteFileApi ManifestFileApi;
|
||||
|
||||
public FileService(DebugService debugService)
|
||||
{
|
||||
_debugService = debugService;
|
||||
|
||||
if(!Directory.Exists(RootPath))
|
||||
Directory.CreateDirectory(RootPath);
|
||||
|
||||
ContentFileApi = CreateFileApi("content");
|
||||
EngineFileApi = CreateFileApi("engine");
|
||||
ManifestFileApi = CreateFileApi("manifest");
|
||||
ConfigurationApi = CreateFileApi("config");
|
||||
}
|
||||
|
||||
public bool CheckMigration(ILoadingHandler loadingHandler)
|
||||
{
|
||||
_debugService.Log("Checking migration...");
|
||||
|
||||
var migrationList = ContentFileApi.AllFiles.Where(f => !f.Contains("\\")).ToList();
|
||||
if(migrationList.Count == 0) return false;
|
||||
|
||||
_debugService.Log($"Found {migrationList.Count} migration files. Starting migration...");
|
||||
Task.Run(() => DoMigration(loadingHandler, migrationList));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DoMigration(ILoadingHandler loadingHandler, List<string> migrationList)
|
||||
{
|
||||
loadingHandler.SetJobsCount(migrationList.Count);
|
||||
|
||||
Parallel.ForEach(migrationList, (f,_)=>MigrateFile(f,loadingHandler));
|
||||
|
||||
if (loadingHandler is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void MigrateFile(string file, ILoadingHandler loadingHandler)
|
||||
{
|
||||
if(!ContentFileApi.TryOpen(file, out var stream))
|
||||
{
|
||||
loadingHandler.AppendResolvedJob();
|
||||
return;
|
||||
}
|
||||
|
||||
ContentFileApi.Save(HashApi.GetManifestPath(file), stream);
|
||||
stream.Dispose();
|
||||
ContentFileApi.Remove(file);
|
||||
loadingHandler.AppendResolvedJob();
|
||||
}
|
||||
|
||||
|
||||
public IReadWriteFileApi CreateFileApi(string path)
|
||||
{
|
||||
return new FileApi(Path.Join(RootPath, path));
|
||||
|
||||
@@ -43,11 +43,21 @@ public sealed class RunnerService(
|
||||
new(hashApi, "/")
|
||||
};
|
||||
|
||||
var module =
|
||||
await engineService.EnsureEngineModules("Robust.Client.WebView", buildInfo.BuildInfo.Build.EngineVersion);
|
||||
if (module is not null)
|
||||
extraMounts.Add(new ApiMount(module, "/"));
|
||||
if (hashApi.TryOpen("manifest.yml", out var stream))
|
||||
{
|
||||
var modules = ContentManifestParser.ExtractModules(stream);
|
||||
|
||||
foreach (var moduleStr in modules)
|
||||
{
|
||||
var module =
|
||||
await engineService.EnsureEngineModules(moduleStr, buildInfo.BuildInfo.Build.EngineVersion);
|
||||
if (module is not null)
|
||||
extraMounts.Add(new ApiMount(module, "/"));
|
||||
}
|
||||
|
||||
await stream.DisposeAsync();
|
||||
}
|
||||
|
||||
var args = new MainArgs(runArgs, engine, redialApi, extraMounts);
|
||||
|
||||
if (!assemblyService.TryOpenAssembly(varService.GetConfigValue(CurrentConVar.RobustAssemblyName)!, engine,
|
||||
@@ -59,4 +69,46 @@ public sealed class RunnerService(
|
||||
|
||||
await Task.Run(() => loader.Main(args), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ContentManifestParser
|
||||
{
|
||||
public static List<string> ExtractModules(Stream manifestStream)
|
||||
{
|
||||
using var reader = new StreamReader(manifestStream);
|
||||
return ExtractModules(reader.ReadToEnd());
|
||||
}
|
||||
|
||||
public static List<string> ExtractModules(string manifestContent)
|
||||
{
|
||||
var modules = new List<string>();
|
||||
var lines = manifestContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
bool inModulesSection = false;
|
||||
|
||||
foreach (var rawLine in lines)
|
||||
{
|
||||
var line = rawLine.Trim();
|
||||
|
||||
if (line.StartsWith("modules:"))
|
||||
{
|
||||
inModulesSection = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inModulesSection)
|
||||
{
|
||||
if (line.StartsWith("- "))
|
||||
{
|
||||
modules.Add(line.Substring(2).Trim());
|
||||
}
|
||||
else if (!line.StartsWith(" "))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user