- tweak: Migrate v3 profiles

This commit is contained in:
2025-07-10 15:22:15 +03:00
parent 83d116003b
commit 34fd4ebf4c
19 changed files with 320 additions and 159 deletions

View File

@@ -1,3 +1,6 @@
namespace Nebula.Shared.Models.Auth;
public sealed record LoginToken(string Token, DateTimeOffset ExpireTime);
public sealed record LoginToken(string Token, DateTimeOffset ExpireTime)
{
public static LoginToken Empty = new(string.Empty, DateTimeOffset.Now);
}

View File

@@ -7,6 +7,7 @@ public interface ILoadingHandler
public void SetResolvedJobsCount(int count);
public int GetResolvedJobsCount();
public void SetLoadingMessage(string message);
public void AppendJob(int count = 1)
{

View File

@@ -1,6 +1,5 @@
using System.Net;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;
using Nebula.Shared.Models.Auth;
using Nebula.Shared.Services.Logging;
@@ -15,15 +14,10 @@ public class AuthService(
CancellationService cancellationService)
{
private readonly HttpClient _httpClient = new();
public CurrentAuthInfo? SelectedAuth { get; private set; }
private readonly ILogger _logger = debugService.GetLogger("AuthService");
public async Task Auth(AuthLoginPassword authLoginPassword, string? code = null)
public async Task<AuthTokenCredentials> Auth(string login, string password, string authServer, string? code = null)
{
var authServer = authLoginPassword.AuthServer;
var login = authLoginPassword.Login;
var password = authLoginPassword.Password;
_logger.Debug($"Auth to {authServer}api/auth/authenticate {login}");
var authUrl = new Uri($"{authServer}api/auth/authenticate");
@@ -34,8 +28,8 @@ public class AuthService(
await restService.PostAsync<AuthenticateResponse, AuthenticateRequest>(
new AuthenticateRequest(login, null, password, code), authUrl, cancellationService.Token);
SelectedAuth = new CurrentAuthInfo(result.UserId,
new LoginToken(result.Token, result.ExpireTime), authLoginPassword.Login, authLoginPassword.AuthServer);
return new AuthTokenCredentials(result.UserId,
new LoginToken(result.Token, result.ExpireTime), login, authServer);
}
catch (RestRequestException e)
{
@@ -48,32 +42,17 @@ public class AuthService(
}
}
public void ClearAuth()
public async Task EnsureToken(AuthTokenCredentials tokenCredentials)
{
SelectedAuth = null;
}
public async Task SetAuth(CurrentAuthInfo info)
{
SelectedAuth = info;
await EnsureToken();
}
public async Task EnsureToken()
{
if (SelectedAuth is null) throw new Exception("Auth info is not set!");
var authUrl = new Uri($"{SelectedAuth.AuthServer}api/auth/ping");
var authUrl = new Uri($"{tokenCredentials.AuthServer}api/auth/ping");
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, authUrl);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("SS14Auth", SelectedAuth.Token.Token);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("SS14Auth", tokenCredentials.Token.Token);
using var resp = await _httpClient.SendAsync(requestMessage, cancellationService.Token);
}
}
public sealed record CurrentAuthInfo(Guid UserId, LoginToken Token, string Login, string AuthServer);
public record AuthLoginPassword(string Login, string Password, string AuthServer);
public sealed record AuthTokenCredentials(Guid UserId, LoginToken Token, string Login, string AuthServer);
public sealed record AuthDenyError(string[] Errors, AuthenticateDenyCode Code);

View File

@@ -0,0 +1,38 @@
using Microsoft.Extensions.DependencyInjection;
using Nebula.Shared.Models;
namespace Nebula.Shared.Services.ConfigMigrations;
public class ProfileMigrationV2(string oldName, string newName)
: BaseConfigurationMigration<ProfileAuthCredentialsV2[], AuthTokenCredentials[]>(oldName, newName)
{
protected override async Task<AuthTokenCredentials[]> Migrate(IServiceProvider serviceProvider, ProfileAuthCredentialsV2[] oldValue, ILoadingHandler loadingHandler)
{
loadingHandler.SetLoadingMessage("Migrating Profile V2 -> V3");
var list = new List<AuthTokenCredentials>();
var authService = serviceProvider.GetRequiredService<AuthService>();
var logger = serviceProvider.GetRequiredService<DebugService>().GetLogger("ProfileMigrationV2");
foreach (var oldCredentials in oldValue)
{
try
{
loadingHandler.SetLoadingMessage($"Migrating {oldCredentials.Login}");
var newCred = await authService.Auth(oldCredentials.Login, oldCredentials.Password, oldCredentials.AuthServer);
list.Add(newCred);
}
catch (Exception e)
{
logger.Error(e);
loadingHandler.SetLoadingMessage(e.Message);
}
}
loadingHandler.SetLoadingMessage("Migration done!");
return list.ToArray();
}
}
public sealed record ProfileAuthCredentialsV2(
string Login,
string Password,
string AuthServer);

View File

@@ -1,6 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Shared.FileApis.Interfaces;
using Nebula.Shared.Models;
using Nebula.Shared.Services.Logging;
using Robust.LoaderApi;
@@ -30,23 +32,116 @@ public static class ConVarBuilder
return new ConVar<T>(name, defaultValue);
}
public static ConVar<T> BuildWithMigration<T>(string name, IConfigurationMigration migration, T? defaultValue = default)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("ConVar name cannot be null or whitespace.", nameof(name));
ConfigurationService.AddConfigurationMigration(migration);
return new ConVar<T>(name, defaultValue);
}
}
public interface IConfigurationMigration
{
public Task DoMigrate(ConfigurationService configurationService, IServiceProvider serviceProvider, ILoadingHandler loadingHandler);
}
public abstract class BaseConfigurationMigration<T1,T2> : IConfigurationMigration
{
protected ConVar<T1> OldConVar;
protected ConVar<T2> NewConVar;
public BaseConfigurationMigration(string oldName, string newName)
{
OldConVar = ConVarBuilder.Build<T1>(oldName);
NewConVar = ConVarBuilder.Build<T2>(newName);
}
public async Task DoMigrate(ConfigurationService configurationService, IServiceProvider serviceProvider, ILoadingHandler loadingHandler)
{
var oldValue = configurationService.GetConfigValue(OldConVar);
if(oldValue == null) return;
var newValue = await Migrate(serviceProvider, oldValue, loadingHandler);
configurationService.SetConfigValue(NewConVar, newValue);
configurationService.ClearConfigValue(OldConVar);
}
protected abstract Task<T2> Migrate(IServiceProvider serviceProvider, T1 oldValue, ILoadingHandler loadingHandler);
}
public class MigrationQueue(List<IConfigurationMigration> migrations) : IConfigurationMigration
{
public async Task DoMigrate(ConfigurationService configurationService, IServiceProvider serviceProvider , ILoadingHandler loadingHandler)
{
foreach (var migration in migrations)
{
await migration.DoMigrate(configurationService, serviceProvider, loadingHandler);
}
}
}
public class MigrationQueueBuilder
{
public static MigrationQueueBuilder Instance => new();
private readonly List<IConfigurationMigration> _migrations = [];
public MigrationQueueBuilder With(IConfigurationMigration migration)
{
_migrations.Add(migration);
return this;
}
public MigrationQueue Build()
{
return new MigrationQueue(_migrations);
}
}
[ServiceRegister]
public class ConfigurationService
{
private readonly IServiceProvider _serviceProvider;
private static List<IConfigurationMigration> _migrations = [];
public static void AddConfigurationMigration(IConfigurationMigration configurationMigration)
{
_migrations.Add(configurationMigration);
}
public delegate void OnConfigurationChangedDelegate<in T>(T value);
public IReadWriteFileApi ConfigurationApi { get; init; }
public IReadWriteFileApi ConfigurationApi { get; }
private readonly ILogger _logger;
public ConfigurationService(FileService fileService, DebugService debugService)
public ConfigurationService(FileService fileService, DebugService debugService, IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_logger = debugService.GetLogger(this);
ConfigurationApi = fileService.CreateFileApi("config");
}
public void MigrateConfigs(ILoadingHandler loadingHandler)
{
Task.Run(async () =>
{
foreach (var migration in _migrations)
{
await migration.DoMigrate(this, _serviceProvider, loadingHandler);
}
if (loadingHandler is IDisposable disposable)
{
disposable.Dispose();
}
});
}
public ConfigChangeSubscriberDisposable<T> SubscribeVarChanged<T>(ConVar<T> convar, OnConfigurationChangedDelegate<T?> @delegate, bool invokeNow = false)
{
convar.OnValueChanged += @delegate;
@@ -112,12 +207,17 @@ public class ConfigurationService
return false;
}
public void ClearConfigValue<T>(ConVar<T> conVar)
{
ConfigurationApi.Remove(GetFileName(conVar));
conVar.OnValueChanged?.Invoke(conVar.DefaultValue);
}
public void SetConfigValue<T>(ConVar<T> conVar, T? value)
{
if (value == null)
{
ConfigurationApi.Remove(GetFileName(conVar));
conVar.OnValueChanged?.Invoke(conVar.DefaultValue);
ClearConfigValue(conVar);
return;
}

View File

@@ -52,6 +52,7 @@ public partial class ContentService
items = allItems.Where(a=> !hashApi.Has(a)).ToList();
loadingHandler.SetLoadingMessage("Download Count:" + items.Count);
_logger.Log("Download Count:" + items.Count);
await Download(downloadUri, items, hashApi, loadingHandler, cancellationToken);
@@ -62,6 +63,7 @@ public partial class ContentService
CancellationToken cancellationToken)
{
_logger.Log("Getting manifest: " + info.Hash);
loadingHandler.SetLoadingMessage("Getting manifest: " + info.Hash);
if (ManifestFileApi.TryOpen(info.Hash, out var stream))
{
@@ -72,6 +74,7 @@ public partial class ContentService
SetServerHash(info.ManifestUri.ToString(), info.Hash);
_logger.Log("Fetching manifest from: " + info.ManifestUri);
loadingHandler.SetLoadingMessage("Fetching manifest from: " + info.ManifestUri);
var response = await _http.GetAsync(info.ManifestUri, cancellationToken);
if (!response.IsSuccessStatusCode) throw new Exception();
@@ -127,6 +130,7 @@ public partial class ContentService
var downloadJobWatch = loadingHandler.GetQueryJob();
loadingHandler.SetLoadingMessage("Downloading from: " + contentCdn);
_logger.Log("Downloading from: " + contentCdn);
var requestBody = new byte[toDownload.Count * 4];
@@ -151,7 +155,7 @@ public partial class ContentService
if (cancellationToken.IsCancellationRequested)
{
_logger.Log("Downloading is cancelled!");
_logger.Log("Downloading cancelled!");
return;
}
@@ -201,7 +205,7 @@ public partial class ContentService
{
if (cancellationToken.IsCancellationRequested)
{
_logger.Log("Downloading is cancelled!");
_logger.Log("Downloading cancelled!");
decompressContext?.Dispose();
compressContext?.Dispose();
return;

View File

@@ -122,6 +122,11 @@ public sealed class ConsoleLoadingHandler : ILoadingHandler
return _resolvedJobs;
}
public void SetLoadingMessage(string message)
{
}
private void UpdatePercent()
{
if (_currJobs == 0)