diff --git a/Nebula.Launcher/LauncherConVar.cs b/Nebula.Launcher/LauncherConVar.cs index aae9892..afd9a61 100644 --- a/Nebula.Launcher/LauncherConVar.cs +++ b/Nebula.Launcher/LauncherConVar.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Globalization; using Nebula.Launcher.Models; using Nebula.Launcher.Models.Auth; -using Nebula.Launcher.ViewModels.Pages; using Nebula.Shared.Services; +using Nebula.Shared.Services.ConfigMigrations; namespace Nebula.Launcher; @@ -11,11 +11,16 @@ public static class LauncherConVar { public static readonly ConVar DoMigration = ConVarBuilder.Build("migration.doMigrate", true); - public static readonly ConVar AuthProfiles = - ConVarBuilder.Build("auth.profiles.v2", []); + + public static readonly ConVar AuthProfiles = + ConVarBuilder.BuildWithMigration("auth.profiles.v3", + MigrationQueueBuilder.Instance + .With(new ProfileMigrationV2("auth.profiles.v2","auth.profiles.v3")) + .Build(), + []); - public static readonly ConVar AuthCurrent = - ConVarBuilder.Build("auth.current.v2"); + public static readonly ConVar AuthCurrent = + ConVarBuilder.Build("auth.current.v2"); public static readonly ConVar Favorites = ConVarBuilder.Build("server.favorites", []); diff --git a/Nebula.Launcher/Models/Auth/ProfileAuthCredentials.cs b/Nebula.Launcher/Models/Auth/ProfileAuthCredentials.cs index dac49e8..e7fe7cb 100644 --- a/Nebula.Launcher/Models/Auth/ProfileAuthCredentials.cs +++ b/Nebula.Launcher/Models/Auth/ProfileAuthCredentials.cs @@ -1,11 +1,10 @@ using System.Text.Json.Serialization; using System.Windows.Input; +using Nebula.Shared.Services; namespace Nebula.Launcher.Models.Auth; public sealed record ProfileAuthCredentials( - string Login, - string Password, - string AuthServer, + AuthTokenCredentials Credentials, [property: JsonIgnore] ICommand OnSelect = default!, [property: JsonIgnore] ICommand OnDelete = default!); \ No newline at end of file diff --git a/Nebula.Launcher/ProcessHelper/GameProcessStartInfoProvider.cs b/Nebula.Launcher/ProcessHelper/GameProcessStartInfoProvider.cs index 65ed509..fd25dd9 100644 --- a/Nebula.Launcher/ProcessHelper/GameProcessStartInfoProvider.cs +++ b/Nebula.Launcher/ProcessHelper/GameProcessStartInfoProvider.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Reflection; using System.Threading.Tasks; +using Nebula.Launcher.ViewModels.Pages; using Nebula.Shared; using Nebula.Shared.Models; using Nebula.Shared.Services; @@ -10,7 +11,7 @@ using Nebula.Shared.Services; namespace Nebula.Launcher.ProcessHelper; [ServiceRegister(isSingleton:false)] -public sealed class GameProcessStartInfoProvider(DotnetResolverService resolverService, AuthService authService) : +public sealed class GameProcessStartInfoProvider(DotnetResolverService resolverService, AccountInfoViewModel accountInfoViewModel) : DotnetProcessStartInfoProviderBase(resolverService) { private string? _publicKey; @@ -34,7 +35,7 @@ public sealed class GameProcessStartInfoProvider(DotnetResolverService resolverS { var baseStart = await base.GetProcessStartInfo(); - var authProv = authService.SelectedAuth; + var authProv = accountInfoViewModel.Credentials; if(authProv is null) throw new Exception("Client is without selected auth"); diff --git a/Nebula.Launcher/ViewModels/MainViewModel.cs b/Nebula.Launcher/ViewModels/MainViewModel.cs index ac8d020..d9a80de 100644 --- a/Nebula.Launcher/ViewModels/MainViewModel.cs +++ b/Nebula.Launcher/ViewModels/MainViewModel.cs @@ -41,17 +41,17 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private bool _popup; [ObservableProperty] private ListItemTemplate? _selectedListItem; - public bool IsLoggedIn => AuthService.SelectedAuth is not null; - public string LoginName => AuthService.SelectedAuth?.Login ?? string.Empty; + public bool IsLoggedIn => AccountInfoViewModel.Credentials is not null; + public string LoginName => AccountInfoViewModel.Credentials?.Login ?? string.Empty; - public string LoginText => Services.LocalisationService.GetString("auth-current-login-name", + public string LoginText => LocalisationService.GetString("auth-current-login-name", new Dictionary { { "login", LoginName } }); - [GenerateProperty] private LocalisationService LocalisationService { get; } - [GenerateProperty] private AuthService AuthService { get; } + [GenerateProperty] private LocalisationService LocalisationService { get; } // Не убирать! Без этой хуйни вся локализация идет в пизду! + [GenerateProperty] private AccountInfoViewModel AccountInfoViewModel { get; } [GenerateProperty] private DebugService DebugService { get; } = default!; [GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!; [GenerateProperty] private ContentService ContentService { get; } = default!; @@ -66,7 +66,7 @@ public partial class MainViewModel : ViewModelBase { Items = new ObservableCollection(_templates.Select(a=> { - return new ListItemTemplate(a.ModelType, a.IconKey, LocalisationService.GetString(a.Label)); + return a with { Label = LocalisationService.GetString(a.Label) }; } )); RequirePage(); @@ -74,6 +74,15 @@ public partial class MainViewModel : ViewModelBase protected override void Initialise() { + AccountInfoViewModel.PropertyChanged += (sender, args) => + { + if (args.PropertyName != nameof(AccountInfoViewModel.Credentials)) + return; + + OnPropertyChanged(nameof(LoginText)); + OnPropertyChanged(nameof(IsLoggedIn)); + }; + _logger = DebugService.GetLogger(this); using var stream = typeof(MainViewModel).Assembly @@ -89,6 +98,11 @@ public partial class MainViewModel : ViewModelBase CheckMigration(); + var loadingHandler = ViewHelperService.GetViewModel(); + loadingHandler.LoadingName = LocalisationService.GetString("migration-config-task"); + loadingHandler.IsCancellable = false; + ConfigurationService.MigrateConfigs(loadingHandler); + if (!VCRuntimeDllChecker.AreVCRuntimeDllsPresent()) { OnPopupRequired(LocalisationService.GetString("vcruntime-check-error")); @@ -146,12 +160,6 @@ public partial class MainViewModel : ViewModelBase CurrentPage = obj; } - public void InvokeChangeAuth() - { - OnPropertyChanged(nameof(IsLoggedIn)); - OnPropertyChanged(nameof(LoginText)); - } - public void PopupMessage(PopupViewModelBase viewModelBase) { if (CurrentPopup == null) @@ -184,6 +192,11 @@ public partial class MainViewModel : ViewModelBase RequirePage(); } + public void OpenRootPath() + { + ExplorerHelper.OpenFolder(FileService.RootPath); + } + public void OpenLink() { Helper.OpenBrowser("https://durenko.tatar/nebula"); diff --git a/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs b/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs index 3885f26..e69145f 100644 --- a/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs @@ -10,6 +10,7 @@ using Nebula.Launcher.Models.Auth; using Nebula.Launcher.Services; using Nebula.Launcher.ViewModels.Popup; using Nebula.Launcher.Views.Pages; +using Nebula.Shared.Models.Auth; using Nebula.Shared.Services; using Nebula.Shared.Services.Logging; using Nebula.Shared.Utils; @@ -35,9 +36,9 @@ public partial class AccountInfoViewModel : ViewModelBase [ObservableProperty] private bool _isLogged; [ObservableProperty] private bool _doRetryAuth; + [ObservableProperty] private AuthTokenCredentials? _credentials; private bool _isProfilesEmpty; - [GenerateProperty] private LocalisationService LocalisationService { get; } [GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!; [GenerateProperty] private ConfigurationService ConfigurationService { get; } = default!; [GenerateProperty] private DebugService DebugService { get; } @@ -54,8 +55,8 @@ public partial class AccountInfoViewModel : ViewModelBase //Design think protected override void InitialiseInDesignMode() { - AddAccount(new AuthLoginPassword("Binka", "12341", "")); - AddAccount(new AuthLoginPassword("Binka", "12341", "")); + AddAccount(new AuthTokenCredentials(Guid.Empty, LoginToken.Empty, "Binka", "")); + AddAccount(new AuthTokenCredentials(Guid.Empty, LoginToken.Empty, "Binka", "")); AuthUrls.Add(new AuthServerCredentials("Test",["example.com"])); } @@ -67,13 +68,31 @@ public partial class AccountInfoViewModel : ViewModelBase Task.Run(ReadAuthConfig); } - public void AuthByProfile(ProfileAuthCredentials credentials) + public async void AuthByProfile(ProfileAuthCredentials credentials) { - CurrentLogin = credentials.Login; - CurrentPassword = credentials.Password; - CurrentAuthServer = credentials.AuthServer; + var message = ViewHelperService.GetViewModel(); + message.InfoText = LocalisationService.GetString("auth-try-auth-profile"); + message.IsInfoClosable = false; + PopupMessageService.Popup(message); - DoAuth(); + try + { + await CatchAuthError(async () => await TryAuth(credentials.Credentials), () => message.Dispose()); + } + catch (Exception ex) + { + CurrentLogin = credentials.Credentials.Login; + CurrentAuthServer = credentials.Credentials.AuthServer; + + var unexpectedError = new Exception(LocalisationService.GetString("auth-error"), ex); + _logger.Error(unexpectedError); + PopupMessageService.Popup(unexpectedError); + return; + } + + ConfigurationService.SetConfigValue(LauncherConVar.AuthCurrent, Credentials); + + message.Dispose(); } public void DoAuth(string? code = null) @@ -117,22 +136,28 @@ public partial class AccountInfoViewModel : ViewModelBase }); } - private async Task TryAuth(CurrentAuthInfo currentAuthInfo) + private async Task TryAuth(AuthTokenCredentials authTokenCredentials) { - CurrentLogin = currentAuthInfo.Login; - CurrentAuthServer = currentAuthInfo.AuthServer; - await AuthService.SetAuth(currentAuthInfo); + CurrentLogin = authTokenCredentials.Login; + CurrentAuthServer = authTokenCredentials.AuthServer; + await SetAuth(authTokenCredentials); IsLogged = true; } + private async Task SetAuth(AuthTokenCredentials authTokenCredentials) + { + await AuthService.EnsureToken(authTokenCredentials); + Credentials = authTokenCredentials; + } + private async Task TryAuth(string login, string password, string authServer, string? code) { - await AuthService.Auth(new AuthLoginPassword(login, password, authServer), code); + Credentials = await AuthService.Auth(login, password, authServer, code); CurrentLogin = login; CurrentPassword = password; CurrentAuthServer = authServer; IsLogged = true; - ConfigurationService.SetConfigValue(LauncherConVar.AuthCurrent, AuthService.SelectedAuth); + ConfigurationService.SetConfigValue(LauncherConVar.AuthCurrent, Credentials); } private async Task CatchAuthError(Func a, Action? onError) @@ -142,7 +167,6 @@ public partial class AccountInfoViewModel : ViewModelBase try { await a(); - ViewHelperService.GetViewModel().InvokeChangeAuth(); } catch (AuthException e) { @@ -200,8 +224,7 @@ public partial class AccountInfoViewModel : ViewModelBase public void Logout() { IsLogged = false; - AuthService.ClearAuth(); - ViewHelperService.GetViewModel().InvokeChangeAuth(); + Credentials = null; } private void UpdateAuthMenu() @@ -212,15 +235,13 @@ public partial class AccountInfoViewModel : ViewModelBase AuthViewSpan = 1; } - private void AddAccount(AuthLoginPassword authLoginPassword) + private void AddAccount(AuthTokenCredentials credentials) { var onDelete = new DelegateCommand(OnDeleteProfile); var onSelect = new DelegateCommand(AuthByProfile); var alpm = new ProfileAuthCredentials( - authLoginPassword.Login, - authLoginPassword.Password, - authLoginPassword.AuthServer, + credentials, onSelect, onDelete); @@ -238,7 +259,7 @@ public partial class AccountInfoViewModel : ViewModelBase PopupMessageService.Popup(message); foreach (var profile in ConfigurationService.GetConfigValue(LauncherConVar.AuthProfiles)!) - AddAccount(new AuthLoginPassword(profile.Login, profile.Password, profile.AuthServer)); + AddAccount(profile); if (Accounts.Count == 0) UpdateAuthMenu(); @@ -281,7 +302,9 @@ public partial class AccountInfoViewModel : ViewModelBase [RelayCommand] private void OnSaveProfile() { - AddAccount(new AuthLoginPassword(CurrentLogin, CurrentPassword, CurrentAuthServer)); + if(Credentials is null) return; + + AddAccount(Credentials); _isProfilesEmpty = Accounts.Count == 0; UpdateAuthMenu(); DirtyProfile(); @@ -322,6 +345,6 @@ public partial class AccountInfoViewModel : ViewModelBase private void DirtyProfile() { ConfigurationService.SetConfigValue(LauncherConVar.AuthProfiles, - Accounts.ToArray()); + Accounts.Select(a => a.Credentials).ToArray()); } } \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/Popup/LoadingContextViewModel.cs b/Nebula.Launcher/ViewModels/Popup/LoadingContextViewModel.cs index 0faed64..71574ad 100644 --- a/Nebula.Launcher/ViewModels/Popup/LoadingContextViewModel.cs +++ b/Nebula.Launcher/ViewModels/Popup/LoadingContextViewModel.cs @@ -15,8 +15,8 @@ public sealed partial class LoadingContextViewModel : PopupViewModelBase, ILoadi [GenerateProperty] public CancellationService CancellationService { get; } [ObservableProperty] private int _currJobs; - [ObservableProperty] private int _resolvedJobs; + [ObservableProperty] private string _message = string.Empty; public string LoadingName { get; set; } = LocalisationService.GetString("popup-loading"); public bool IsCancellable { get; set; } = true; @@ -44,6 +44,11 @@ public sealed partial class LoadingContextViewModel : PopupViewModelBase, ILoadi return ResolvedJobs; } + public void SetLoadingMessage(string message) + { + Message = message + "\n" + Message; + } + public void Cancel(){ if(!IsCancellable) return; CancellationService.Cancel(); @@ -56,5 +61,30 @@ public sealed partial class LoadingContextViewModel : PopupViewModelBase, ILoadi protected override void InitialiseInDesignMode() { + SetJobsCount(5); + SetResolvedJobsCount(2); + string[] debugMessages = { + "Debug: Starting phase 1...", + "Debug: Loading assets...", + "Debug: Connecting to server...", + "Debug: Fetching user data...", + "Debug: Applying configurations...", + "Debug: Starting phase 2...", + "Debug: Rendering UI...", + "Debug: Preparing scene...", + "Debug: Initializing components...", + "Debug: Running diagnostics...", + "Debug: Checking dependencies...", + "Debug: Verifying files...", + "Debug: Cleaning up cache...", + "Debug: Finalizing setup...", + "Debug: Setup complete.", + "Debug: Ready for launch." + }; + + foreach (string message in debugMessages) + { + SetLoadingMessage(message); + } } } \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs index a713177..84a7d78 100644 --- a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs +++ b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs @@ -47,7 +47,7 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis public LogPopupModelView CurrLog; public RobustUrl Address { get; private set; } - [GenerateProperty] private AuthService AuthService { get; } + [GenerateProperty] private AccountInfoViewModel AccountInfoViewModel { get; } [GenerateProperty] private CancellationService CancellationService { get; } = default!; [GenerateProperty] private DebugService DebugService { get; } = default!; [GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!; @@ -174,7 +174,7 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis private async Task RunInstanceAsync(bool ignoreLoginCredentials = false) { - if (!ignoreLoginCredentials && AuthService.SelectedAuth is null) + if (!ignoreLoginCredentials && AccountInfoViewModel.Credentials is null) { var warningContext = ViewHelperService.GetViewModel() .WithServerEntry(this); diff --git a/Nebula.Launcher/Views/MainView.axaml b/Nebula.Launcher/Views/MainView.axaml index 36cfac3..4811a02 100644 --- a/Nebula.Launcher/Views/MainView.axaml +++ b/Nebula.Launcher/Views/MainView.axaml @@ -127,10 +127,30 @@ Padding="0" CornerRadius="0" Command="{Binding OpenAuthPage}"> - - - - + + + + + + + + + | + | diff --git a/Nebula.Launcher/Views/Pages/AccountInfoView.axaml b/Nebula.Launcher/Views/Pages/AccountInfoView.axaml index 3a3e4cb..f708cb9 100644 --- a/Nebula.Launcher/Views/Pages/AccountInfoView.axaml +++ b/Nebula.Launcher/Views/Pages/AccountInfoView.axaml @@ -53,7 +53,7 @@ @@ -156,14 +156,6 @@ - - - + + + + + \ No newline at end of file diff --git a/Nebula.Runner/App.cs b/Nebula.Runner/App.cs index 2248f8e..79a7671 100644 --- a/Nebula.Runner/App.cs +++ b/Nebula.Runner/App.cs @@ -47,65 +47,3 @@ public sealed class App(RunnerService runnerService, ContentService contentServi await runnerService.Run(args.ToArray(), buildInfo, this, new ConsoleLoadingHandler(), cancelTokenSource.Token); } } - -public sealed class ConsoleLoadingHandler : ILoadingHandler -{ - private int _currJobs; - - private float _percent; - private int _resolvedJobs; - - public void SetJobsCount(int count) - { - _currJobs = count; - - UpdatePercent(); - Draw(); - } - - public int GetJobsCount() - { - return _currJobs; - } - - public void SetResolvedJobsCount(int count) - { - _resolvedJobs = count; - - UpdatePercent(); - Draw(); - } - - public int GetResolvedJobsCount() - { - return _resolvedJobs; - } - - private void UpdatePercent() - { - if (_currJobs == 0) - { - _percent = 0; - return; - } - - if (_resolvedJobs > _currJobs) return; - - _percent = _resolvedJobs / (float)_currJobs; - } - - private void Draw() - { - var barCount = 10; - var fullCount = (int)(barCount * _percent); - var emptyCount = barCount - fullCount; - - Console.Write("\r"); - - for (var i = 0; i < fullCount; i++) Console.Write("#"); - - for (var i = 0; i < emptyCount; i++) Console.Write(" "); - - Console.Write($"\t {_resolvedJobs}/{_currJobs}"); - } -} \ No newline at end of file diff --git a/Nebula.Shared/Models/Auth/LoginToken.cs b/Nebula.Shared/Models/Auth/LoginToken.cs index e7b69fd..687e312 100644 --- a/Nebula.Shared/Models/Auth/LoginToken.cs +++ b/Nebula.Shared/Models/Auth/LoginToken.cs @@ -1,3 +1,6 @@ namespace Nebula.Shared.Models.Auth; -public sealed record LoginToken(string Token, DateTimeOffset ExpireTime); \ No newline at end of file +public sealed record LoginToken(string Token, DateTimeOffset ExpireTime) +{ + public static LoginToken Empty = new(string.Empty, DateTimeOffset.Now); +} \ No newline at end of file diff --git a/Nebula.Shared/Models/ILoadingHandler.cs b/Nebula.Shared/Models/ILoadingHandler.cs index c199b92..bf3a0d6 100644 --- a/Nebula.Shared/Models/ILoadingHandler.cs +++ b/Nebula.Shared/Models/ILoadingHandler.cs @@ -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) { diff --git a/Nebula.Shared/Services/AuthService.cs b/Nebula.Shared/Services/AuthService.cs index 1f9a846..8332f7e 100644 --- a/Nebula.Shared/Services/AuthService.cs +++ b/Nebula.Shared/Services/AuthService.cs @@ -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 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( 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); diff --git a/Nebula.Shared/Services/ConfigMigrations/ProfileMigration.cs b/Nebula.Shared/Services/ConfigMigrations/ProfileMigration.cs new file mode 100644 index 0000000..88ba322 --- /dev/null +++ b/Nebula.Shared/Services/ConfigMigrations/ProfileMigration.cs @@ -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(oldName, newName) +{ + protected override async Task Migrate(IServiceProvider serviceProvider, ProfileAuthCredentialsV2[] oldValue, ILoadingHandler loadingHandler) + { + loadingHandler.SetLoadingMessage("Migrating Profile V2 -> V3"); + var list = new List(); + var authService = serviceProvider.GetRequiredService(); + var logger = serviceProvider.GetRequiredService().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); \ No newline at end of file diff --git a/Nebula.Shared/Services/ConfigurationService.cs b/Nebula.Shared/Services/ConfigurationService.cs index 3361962..72d130d 100644 --- a/Nebula.Shared/Services/ConfigurationService.cs +++ b/Nebula.Shared/Services/ConfigurationService.cs @@ -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(name, defaultValue); } + + public static ConVar BuildWithMigration(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(name, defaultValue); + } +} + +public interface IConfigurationMigration +{ + public Task DoMigrate(ConfigurationService configurationService, IServiceProvider serviceProvider, ILoadingHandler loadingHandler); +} + +public abstract class BaseConfigurationMigration : IConfigurationMigration +{ + protected ConVar OldConVar; + protected ConVar NewConVar; + + public BaseConfigurationMigration(string oldName, string newName) + { + OldConVar = ConVarBuilder.Build(oldName); + NewConVar = ConVarBuilder.Build(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 Migrate(IServiceProvider serviceProvider, T1 oldValue, ILoadingHandler loadingHandler); +} + +public class MigrationQueue(List 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 _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 _migrations = []; + + public static void AddConfigurationMigration(IConfigurationMigration configurationMigration) + { + _migrations.Add(configurationMigration); + } + public delegate void OnConfigurationChangedDelegate(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 SubscribeVarChanged(ConVar convar, OnConfigurationChangedDelegate @delegate, bool invokeNow = false) { convar.OnValueChanged += @delegate; @@ -112,12 +207,17 @@ public class ConfigurationService return false; } + public void ClearConfigValue(ConVar conVar) + { + ConfigurationApi.Remove(GetFileName(conVar)); + conVar.OnValueChanged?.Invoke(conVar.DefaultValue); + } + public void SetConfigValue(ConVar conVar, T? value) { if (value == null) { - ConfigurationApi.Remove(GetFileName(conVar)); - conVar.OnValueChanged?.Invoke(conVar.DefaultValue); + ClearConfigValue(conVar); return; } diff --git a/Nebula.Shared/Services/ContentService.Download.cs b/Nebula.Shared/Services/ContentService.Download.cs index 84f89db..71f79e8 100644 --- a/Nebula.Shared/Services/ContentService.Download.cs +++ b/Nebula.Shared/Services/ContentService.Download.cs @@ -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; diff --git a/Nebula.Shared/Services/FileService.cs b/Nebula.Shared/Services/FileService.cs index 09831e4..10d95d4 100644 --- a/Nebula.Shared/Services/FileService.cs +++ b/Nebula.Shared/Services/FileService.cs @@ -122,6 +122,11 @@ public sealed class ConsoleLoadingHandler : ILoadingHandler return _resolvedJobs; } + public void SetLoadingMessage(string message) + { + + } + private void UpdatePercent() { if (_currJobs == 0) diff --git a/Nebula.sln.DotSettings.user b/Nebula.sln.DotSettings.user index dbd1490..114714c 100644 --- a/Nebula.sln.DotSettings.user +++ b/Nebula.sln.DotSettings.user @@ -1,6 +1,7 @@  ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -26,6 +27,8 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -33,6 +36,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -40,6 +44,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded