- shit: Cleanup this mess

This commit is contained in:
2025-01-14 22:10:16 +03:00
parent a0b2cfd677
commit 08e518602b
71 changed files with 1022 additions and 943 deletions

View File

@@ -1,22 +1,19 @@
using System;
using System.Linq;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using System.Linq;
using System.Reflection;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Launcher.ViewModels;
using Nebula.Launcher.Views;
using Nebula.Shared;
namespace Nebula.Launcher;
public partial class App : Application
public class App : Application
{
private IServiceProvider _serviceProvider = null!;
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
@@ -26,11 +23,11 @@ public partial class App : Application
{
var services = new ServiceCollection();
services.AddAvaloniaServices();
services.AddViews();
services.AddServices();
services.AddViews();
_serviceProvider = services.BuildServiceProvider();
switch (ApplicationLifetime)
{
case IClassicDesktopStyleApplicationLifetime desktop:
@@ -52,9 +49,6 @@ public partial class App : Application
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
// remove each entry found
foreach (var plugin in dataValidationPluginsToRemove)
{
BindingPlugins.DataValidators.Remove(plugin);
}
foreach (var plugin in dataValidationPluginsToRemove) BindingPlugins.DataValidators.Remove(plugin);
}
}

View File

@@ -57,7 +57,7 @@
<Setter Property="Padding" Value="8" />
<Setter Property="Background" Value="#00000000" />
</Style>
<Style Selector="ListBox.AccountSelector > ListBoxItem">
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Margin" Value="0,0,0,0" />
@@ -65,8 +65,8 @@
<Setter Property="Background" Value="#00000000" />
<Setter Property="Focusable" Value="False" />
</Style>
<Style Selector="ListBox.AccountSelector > ListBoxItem:selected">
<Setter Property="Background" Value="#00000000" />
</Style>
</Styles>
</Styles>

View File

@@ -9,7 +9,7 @@ public class TypeConverters
{
private const string StreamGeometryNotFound =
"M24 4C35.0457 4 44 12.9543 44 24C44 35.0457 35.0457 44 24 44C12.9543 44 4 35.0457 4 24C4 12.9543 12.9543 4 24 4ZM24 6.5C14.335 6.5 6.5 14.335 6.5 24C6.5 33.665 14.335 41.5 24 41.5C33.665 41.5 41.5 33.665 41.5 24C41.5 14.335 33.665 6.5 24 6.5ZM24.25 32C25.0784 32 25.75 32.6716 25.75 33.5C25.75 34.3284 25.0784 35 24.25 35C23.4216 35 22.75 34.3284 22.75 33.5C22.75 32.6716 23.4216 32 24.25 32ZM24.25 13C27.6147 13 30.5 15.8821 30.5 19.2488C30.502 21.3691 29.7314 22.7192 27.8216 24.7772L26.8066 25.8638C25.7842 27.0028 25.3794 27.7252 25.3409 28.5793L25.3379 28.7411L25.3323 28.8689L25.3143 28.9932C25.2018 29.5636 24.7009 29.9957 24.0968 30.0001C23.4065 30.0049 22.8428 29.4493 22.8379 28.7589C22.8251 26.9703 23.5147 25.7467 25.1461 23.9739L26.1734 22.8762C27.5312 21.3837 28.0012 20.503 28 19.25C28 17.2634 26.2346 15.5 24.25 15.5C22.3307 15.5 20.6142 17.1536 20.5055 19.0587L20.4935 19.3778C20.4295 20.0081 19.8972 20.5 19.25 20.5C18.5596 20.5 18 19.9404 18 19.25C18 15.8846 20.8864 13 24.25 13Z";
public static FuncValueConverter<string, StreamGeometry> IconConverter { get; } =
new(iconKey =>
{

View File

@@ -0,0 +1,2 @@
global using SourceGen;
global using Nebula.Launcher.ViewHelper;

View File

@@ -10,33 +10,33 @@
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.3.0" />
<PackageReference Include="Avalonia" Version="11.2.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.2.1" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.1" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.1" />
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.3.0"/>
<PackageReference Include="Avalonia" Version="11.2.1"/>
<PackageReference Include="Avalonia.Desktop" Version="11.2.1"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.1"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.1"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.1">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="libsodium" Version="1.0.20" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1"/>
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0"/>
<PackageReference Include="libsodium" Version="1.0.20"/>
</ItemGroup>
<ItemGroup>
<Compile Update="Views\Tabs\ServerListTab.axaml.cs">
<DependentUpon>ServerListTab.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Views\Tabs\ServerListTab.axaml.cs">
<DependentUpon>ServerListTab.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<Target Name="BuildCheck" AfterTargets="AfterBuild">
<Copy SourceFiles="..\Nebula.Runner\bin\$(Configuration)\$(TargetFramework)\Nebula.Runner.dll" DestinationFolder="$(OutDir)"/>
<Copy SourceFiles="..\Nebula.Runner\bin\$(Configuration)\$(TargetFramework)\Nebula.Runner.pdb" DestinationFolder="$(OutDir)"/>
@@ -45,6 +45,7 @@
</Target>
<ItemGroup>
<ProjectReference Include="..\Nebula.Shared\Nebula.Shared.csproj" />
<ProjectReference Include="..\Nebula.Shared\Nebula.Shared.csproj"/>
<ProjectReference Include="..\Nebula.SourceGenerators\Nebula.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
</Project>

View File

@@ -4,13 +4,18 @@ namespace Nebula.Launcher;
public static class Program
{
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
public static void Main(string[] args)
{
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer.
private static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
}

View File

@@ -6,10 +6,7 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.ViewModels;
using Nebula.Launcher.Views;
using Nebula.Launcher.Views.Pages;
namespace Nebula.Launcher;
@@ -18,14 +15,20 @@ public static class ServiceCollectionExtensions
public static void AddAvaloniaServices(this IServiceCollection services)
{
services.AddSingleton<IDispatcher>(_ => Dispatcher.UIThread);
services.AddSingleton(_ => Application.Current?.ApplicationLifetime ?? throw new InvalidOperationException("No application lifetime is set"));
services.AddSingleton(_ =>
Application.Current?.ApplicationLifetime ??
throw new InvalidOperationException("No application lifetime is set"));
services.AddSingleton(sp =>
sp.GetRequiredService<IApplicationLifetime>() switch
{
IClassicDesktopStyleApplicationLifetime desktop => desktop.MainWindow ?? throw new InvalidOperationException("No main window set"),
ISingleViewApplicationLifetime singleViewPlatform => TopLevel.GetTopLevel(singleViewPlatform.MainView) ?? throw new InvalidOperationException("Could not find top level element for single view"),
_ => throw new InvalidOperationException($"Could not find {nameof(TopLevel)} element"),
IClassicDesktopStyleApplicationLifetime desktop => desktop.MainWindow ??
throw new InvalidOperationException(
"No main window set"),
ISingleViewApplicationLifetime singleViewPlatform =>
TopLevel.GetTopLevel(singleViewPlatform.MainView) ??
throw new InvalidOperationException("Could not find top level element for single view"),
_ => throw new InvalidOperationException($"Could not find {nameof(TopLevel)} element")
}
);
@@ -38,20 +41,18 @@ public static class ServiceCollectionExtensions
foreach (var (viewModel, view, isSingleton) in GetTypesWithHelpAttribute(Assembly.GetExecutingAssembly()))
{
if(isSingleton)services.AddSingleton(viewModel);
if (isSingleton) services.AddSingleton(viewModel);
else services.AddTransient(viewModel);
if (view != null) services.AddTransient(view);
}
}
private static IEnumerable<(Type,Type?,bool)> GetTypesWithHelpAttribute(Assembly assembly) {
foreach(Type type in assembly.GetTypes())
private static IEnumerable<(Type, Type?, bool)> GetTypesWithHelpAttribute(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
{
var attr = type.GetCustomAttribute<ViewModelRegisterAttribute>();
if (attr is not null) {
yield return (type, attr.Type, attr.IsSingleton);
}
if (attr is not null) yield return (type, attr.Type, attr.IsSingleton);
}
}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Nebula.Launcher.ViewModels;
using Nebula.Shared;
namespace Nebula.Launcher.Services;
[ServiceRegister, ConstructGenerator]
public sealed partial class ViewHelperService
{
[GenerateProperty] private IServiceProvider ServiceProvider { get; } = default!;
public bool TryGetViewModel(Type type, [NotNullWhen(true)] out ViewModelBase? viewModelBase)
{
viewModelBase = null;
var vm = Design.IsDesignMode
? Activator.CreateInstance(type)
: ServiceProvider.GetService(type);
if (vm is not ViewModelBase vmb) return false;
viewModelBase = vmb;
return true;
}
public bool TryGetViewModel<T>([NotNullWhen(true)] out T? viewModelBase) where T : ViewModelBase
{
var success = TryGetViewModel(typeof(T), out var vmb);
viewModelBase = (T?)vmb;
return success;
}
public T GetViewModel<T>() where T : ViewModelBase
{
TryGetViewModel<T>(out var viewModelBase);
return viewModelBase!;
}
private void Initialise(){}
private void InitialiseInDesignMode(){}
}

View File

@@ -5,12 +5,12 @@ namespace Nebula.Launcher.ViewHelper;
[AttributeUsage(AttributeTargets.Class)]
public class ViewModelRegisterAttribute : Attribute
{
public Type? Type { get; }
public bool IsSingleton { get; }
public ViewModelRegisterAttribute(Type? type = null, bool isSingleton = true)
{
Type = type;
IsSingleton = isSingleton;
}
public Type? Type { get; }
public bool IsSingleton { get; }
}

View File

@@ -2,7 +2,6 @@ using System;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.ViewModels;
namespace Nebula.Launcher;
@@ -13,15 +12,12 @@ public class ViewLocator : IDataTemplate
{
if (param is null)
return null;
var type = param.GetType().GetCustomAttribute<ViewModelRegisterAttribute>()?.Type;
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
if (type != null) return (Control)Activator.CreateInstance(type)!;
return new TextBlock { Text = "Not Found: " + param.GetType()};
return new TextBlock { Text = "Not Found: " + param.GetType() };
}
public bool Match(object? data)

View File

@@ -1,24 +0,0 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.Views.Popup;
namespace Nebula.Launcher.ViewModels;
[ViewModelRegister(typeof(InfoPopupView), false)]
public partial class InfoPopupViewModel : PopupViewModelBase
{
public InfoPopupViewModel()
{
}
public InfoPopupViewModel(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
public override string Title => "Info";
public override bool IsClosable => true;
[ObservableProperty]
private string _infoText = "Test";
}

View File

@@ -4,8 +4,9 @@ using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using JetBrains.Annotations;
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.Services;
using Nebula.Launcher.ViewModels.Pages;
using Nebula.Launcher.ViewModels.Popup;
using Nebula.Launcher.Views;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
@@ -14,34 +15,9 @@ using Nebula.Shared.Utils;
namespace Nebula.Launcher.ViewModels;
[ViewModelRegister(typeof(MainView))]
[ConstructGenerator]
public partial class MainViewModel : ViewModelBase
{
public MainViewModel()
{
TryGetViewModel(typeof(AccountInfoViewModel), out var model);
_currentPage = model!;
Items = new ObservableCollection<ListItemTemplate>(_templates);
SelectedListItem = Items.First(vm => vm.ModelType == typeof(AccountInfoViewModel));
}
[UsedImplicitly]
public MainViewModel(AccountInfoViewModel accountInfoViewModel,DebugService debugService, PopupMessageService popupMessageService,
IServiceProvider serviceProvider): base(serviceProvider)
{
_currentPage = accountInfoViewModel;
_debugService = debugService;
Items = new ObservableCollection<ListItemTemplate>(_templates);
popupMessageService.OnPopupRequired += OnPopupRequired;
popupMessageService.OnCloseRequired += OnPopupCloseRequired;
SelectedListItem = Items.First(vm => vm.ModelType == typeof(AccountInfoViewModel));
}
private readonly List<PopupViewModelBase> _viewQueue = new();
private readonly List<ListItemTemplate> _templates =
[
new ListItemTemplate(typeof(AccountInfoViewModel), "Account", "Account"),
@@ -49,41 +25,57 @@ public partial class MainViewModel : ViewModelBase
new ListItemTemplate(typeof(ContentBrowserViewModel), "GridRegular", "Content")
];
[ObservableProperty]
private bool _isPaneOpen;
private readonly List<PopupViewModelBase> _viewQueue = new();
[ObservableProperty]
private ViewModelBase _currentPage;
[ObservableProperty] private ViewModelBase _currentPage;
private readonly DebugService _debugService;
[ObservableProperty] private PopupViewModelBase? _currentPopup;
[ObservableProperty] private string _currentTitle = "Default";
[ObservableProperty] private bool _isEnabled = true;
[ObservableProperty] private bool _popup;
[ObservableProperty]
private PopupViewModelBase? _currentPopup;
[ObservableProperty]
private string _currentTitle = "Default";
[ObservableProperty] private bool _isPaneOpen;
[ObservableProperty] private bool _isPopupClosable = true;
[ObservableProperty] private bool _popup;
[ObservableProperty]
private ListItemTemplate? _selectedListItem;
[ObservableProperty] private ListItemTemplate? _selectedListItem;
[GenerateProperty] private DebugService DebugService { get; } = default!;
[GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!;
[GenerateProperty] private ViewHelperService ViewHelperService { get; } = default!;
public ObservableCollection<ListItemTemplate> Items { get; private set; }
protected override void InitialiseInDesignMode()
{
CurrentPage = ViewHelperService.GetViewModel<AccountInfoViewModel>();
Items = new ObservableCollection<ListItemTemplate>(_templates);
SelectedListItem = Items.First(vm => vm.ModelType == typeof(AccountInfoViewModel));
}
protected override void Initialise()
{
CurrentPage = ViewHelperService.GetViewModel<AccountInfoViewModel>();
Items = new ObservableCollection<ListItemTemplate>(_templates);
PopupMessageService.OnPopupRequired += OnPopupRequired;
PopupMessageService.OnCloseRequired += OnPopupCloseRequired;
SelectedListItem = Items.First(vm => vm.ModelType == typeof(AccountInfoViewModel));
}
partial void OnSelectedListItemChanged(ListItemTemplate? value)
{
if (value is null) return;
if(!TryGetViewModel(value.ModelType, out var vmb))
{
return;
}
if (!ViewHelperService.TryGetViewModel(value.ModelType, out var vmb)) return;
CurrentPage = vmb;
}
public ObservableCollection<ListItemTemplate> Items { get; }
public void PopupMessage(PopupViewModelBase viewModelBase)
{
if (CurrentPopup == null)
@@ -98,7 +90,7 @@ public partial class MainViewModel : ViewModelBase
_viewQueue.Add(viewModelBase);
}
}
private void OnCloseRequired()
{
IsEnabled = true;
@@ -122,7 +114,7 @@ public partial class MainViewModel : ViewModelBase
{
case string str:
{
var view = GetViewModel<InfoPopupViewModel>();
var view = ViewHelperService.GetViewModel<InfoPopupViewModel>();
view.InfoText = str;
PopupMessage(view);
break;
@@ -131,21 +123,18 @@ public partial class MainViewModel : ViewModelBase
PopupMessage(@base);
break;
case Exception error:
var err = GetViewModel<ExceptionViewModel>();
_debugService.Error(error);
var err = ViewHelperService.GetViewModel<ExceptionViewModel>();
DebugService.Error(error);
err.AppendError(error);
PopupMessage(err);
break;
}
}
private void OnPopupCloseRequired(object obj)
{
if(obj is not PopupViewModelBase viewModelBase)
{
return;
}
if (obj is not PopupViewModelBase viewModelBase) return;
if (obj == CurrentPopup)
ClosePopup();
else
@@ -158,20 +147,21 @@ public partial class MainViewModel : ViewModelBase
{
IsPaneOpen = !IsPaneOpen;
}
[RelayCommand]
public void ClosePopup()
{
var viewModelBase = _viewQueue.FirstOrDefault();
if (viewModelBase is null)
{
OnCloseRequired();
}
else
{
CurrentTitle = viewModelBase.Title;
_viewQueue.RemoveAt(0);
}
CurrentPopup = viewModelBase;
}
}
}

View File

@@ -1,46 +1,44 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.Services;
using Nebula.Launcher.ViewModels.Popup;
using Nebula.Launcher.Views.Pages;
using Nebula.Shared;
using Nebula.Shared.Services;
using Nebula.Shared.Utils;
namespace Nebula.Launcher.ViewModels;
namespace Nebula.Launcher.ViewModels.Pages;
[ViewModelRegister(typeof(AccountInfoView))]
[ConstructGenerator]
public partial class AccountInfoViewModel : ViewModelBase
{
private readonly PopupMessageService _popupMessageService;
private readonly ConfigurationService _configurationService;
private readonly AuthService _authService;
public ObservableCollection<AuthLoginPasswordModel> Accounts { get; } = new();
public ObservableCollection<string> AuthUrls { get; } = new();
[ObservableProperty]
private string _currentLogin = String.Empty;
[ObservableProperty]
private string _currentPassword = String.Empty;
[ObservableProperty]
private string _currentAuthServer = String.Empty;
[ObservableProperty] private bool _authMenuExpand;
[ObservableProperty] private bool _authUrlConfigExpand;
[ObservableProperty] private int _authViewSpan = 1;
[ObservableProperty] private bool _authMenuExpand;
private bool _isProfilesEmpty;
[ObservableProperty] private string _currentAuthServer = string.Empty;
[ObservableProperty] private string _currentLogin = string.Empty;
[ObservableProperty] private string _currentPassword = string.Empty;
[ObservableProperty] private bool _isLogged;
private bool _isProfilesEmpty;
[GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!;
[GenerateProperty] private ConfigurationService ConfigurationService { get; } = default!;
[GenerateProperty] private AuthService AuthService { get; } = default!;
[GenerateProperty] private ViewHelperService ViewHelperService { get; } = default!;
public ObservableCollection<AuthLoginPasswordModel> Accounts { get; } = new();
public ObservableCollection<string> AuthUrls { get; } = new();
private AuthLoginPassword CurrentAlp
{
get => new(CurrentLogin, CurrentPassword, CurrentAuthServer);
@@ -51,24 +49,23 @@ public partial class AccountInfoViewModel : ViewModelBase
CurrentAuthServer = value.AuthServer;
}
}
//Design think
public AccountInfoViewModel()
public string AuthItemSelect
{
AddAccount(new AuthLoginPassword("Binka","12341",""));
set => CurrentAuthServer = value;
}
//Design think
protected override void InitialiseInDesignMode()
{
AddAccount(new AuthLoginPassword("Binka", "12341", ""));
AuthUrls.Add("https://cinka.ru");
AuthUrls.Add("https://cinka.ru");
}
//Real think
public AccountInfoViewModel(IServiceProvider serviceProvider, PopupMessageService popupMessageService,
ConfigurationService configurationService, AuthService authService) : base(serviceProvider)
{
//_popupMessageService = mainViewModel;
_popupMessageService = popupMessageService;
_configurationService = configurationService;
_authService = authService;
//Real think
protected override void Initialise()
{
ReadAuthConfig();
}
@@ -80,88 +77,75 @@ public partial class AccountInfoViewModel : ViewModelBase
public async void DoAuth()
{
var message = GetViewModel<InfoPopupViewModel>();
var message = ViewHelperService.GetViewModel<InfoPopupViewModel>();
message.InfoText = "Auth think, please wait...";
_popupMessageService.Popup(message);
if(await _authService.Auth(CurrentAlp))
PopupMessageService.Popup(message);
if (await AuthService.Auth(CurrentAlp))
{
message.Dispose();
IsLogged = true;
_configurationService.SetConfigValue(CurrentConVar.AuthCurrent, CurrentAlp);
ConfigurationService.SetConfigValue(CurrentConVar.AuthCurrent, CurrentAlp);
}
else
{
message.Dispose();
Logout();
_popupMessageService.Popup("Well, shit is happened: " + _authService.Reason);
PopupMessageService.Popup("Well, shit is happened: " + AuthService.Reason);
}
}
public void Logout()
{
IsLogged = false;
//CurrentAlp = new AuthLoginPassword("", "", "");
_authService.ClearAuth();
AuthService.ClearAuth();
}
private void UpdateAuthMenu()
{
if (AuthMenuExpand || _isProfilesEmpty)
{
AuthViewSpan = 2;
}
else
{
AuthViewSpan = 1;
}
}
private void AddAccount(AuthLoginPassword authLoginPassword)
{
var onDelete = new DelegateCommand<AuthLoginPasswordModel>(OnDeleteProfile);
var onSelect = new DelegateCommand<AuthLoginPasswordModel>(AuthByAlp);
var alpm = new AuthLoginPasswordModel(
authLoginPassword.Login,
authLoginPassword.Login,
authLoginPassword.Password,
authLoginPassword.AuthServer,
onSelect,
authLoginPassword.AuthServer,
onSelect,
onDelete);
onDelete.TRef.Value = alpm;
onSelect.TRef.Value = alpm;
Accounts.Add(alpm);
}
private void ReadAuthConfig()
{
foreach (var profile in
_configurationService.GetConfigValue(CurrentConVar.AuthProfiles)!)
{
foreach (var profile in
ConfigurationService.GetConfigValue(CurrentConVar.AuthProfiles)!)
AddAccount(profile);
}
if (Accounts.Count == 0)
{
UpdateAuthMenu();
}
if (Accounts.Count == 0) UpdateAuthMenu();
var currProfile = _configurationService.GetConfigValue(CurrentConVar.AuthCurrent);
var currProfile = ConfigurationService.GetConfigValue(CurrentConVar.AuthCurrent);
if (currProfile != null)
{
CurrentAlp = currProfile;
DoAuth();
}
AuthUrls.Clear();
var authUrls = _configurationService.GetConfigValue(CurrentConVar.AuthServers)!;
foreach (var url in authUrls)
{
AuthUrls.Add(url);
}
var authUrls = ConfigurationService.GetConfigValue(CurrentConVar.AuthServers)!;
foreach (var url in authUrls) AuthUrls.Add(url);
}
[RelayCommand]
@@ -172,7 +156,7 @@ public partial class AccountInfoViewModel : ViewModelBase
UpdateAuthMenu();
DirtyProfile();
}
private void OnDeleteProfile(AuthLoginPasswordModel account)
{
Accounts.Remove(account);
@@ -196,15 +180,15 @@ public partial class AccountInfoViewModel : ViewModelBase
private void DirtyProfile()
{
_configurationService.SetConfigValue(CurrentConVar.AuthProfiles,
Accounts.Select(a => (AuthLoginPassword) a).ToArray());
}
public string AuthItemSelect
{
set => CurrentAuthServer = value;
ConfigurationService.SetConfigValue(CurrentConVar.AuthProfiles,
Accounts.Select(a => (AuthLoginPassword)a).ToArray());
}
}
public record AuthLoginPasswordModel(string Login, string Password, string AuthServer, ICommand OnSelect = default!, ICommand OnDelete = default!)
public record AuthLoginPasswordModel(
string Login,
string Password,
string AuthServer,
ICommand OnSelect = default!,
ICommand OnDelete = default!)
: AuthLoginPassword(Login, Password, AuthServer);

View File

@@ -11,35 +11,37 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.Services;
using Nebula.Launcher.ViewModels.Popup;
using Nebula.Launcher.Views.Pages;
using Nebula.Shared;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
using Nebula.Shared.Utils;
namespace Nebula.Launcher.ViewModels;
namespace Nebula.Launcher.ViewModels.Pages;
[ViewModelRegister(typeof(ContentBrowserView))]
[ConstructGenerator]
public sealed partial class ContentBrowserViewModel : ViewModelBase
{
private readonly IServiceProvider _provider;
private readonly ContentService _contentService;
private readonly CancellationService _cancellationService;
private readonly FileService _fileService;
private readonly DebugService _debugService;
private readonly PopupMessageService _popupService;
public ObservableCollection<ContentEntry> Entries { get; } = new();
private readonly List<ContentEntry> _root = new();
private List<string> _history = new();
private readonly List<string> _history = new();
[ObservableProperty] private string _message = "";
[ObservableProperty] private string _searchText = "";
[ObservableProperty] private string _serverText = "";
private ContentEntry? _selectedEntry;
[ObservableProperty] private string _serverText = "";
[GenerateProperty] private ContentService ContentService { get; } = default!;
[GenerateProperty] private CancellationService CancellationService { get; } = default!;
[GenerateProperty] private FileService FileService { get; } = default!;
[GenerateProperty] private DebugService DebugService { get; } = default!;
[GenerateProperty] private PopupMessageService PopupService { get; } = default!;
[GenerateProperty] private HubService HubService { get; } = default!;
[GenerateProperty] private ViewHelperService ViewHelperService { get; } = default!;
public ObservableCollection<ContentEntry> Entries { get; } = new();
public ContentEntry? SelectedEntry
{
@@ -48,94 +50,73 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase
{
if (value is { Item: not null })
{
if (_fileService.ContentFileApi.TryOpen(value.Item.Value.Hash, out var stream))
if (FileService.ContentFileApi.TryOpen(value.Item.Value.Hash, out var stream))
{
var ext = Path.GetExtension(value.Item.Value.Path);
var myTempFile = Path.Combine(Path.GetTempPath(), "tempie"+ ext);
using(var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None))
var myTempFile = Path.Combine(Path.GetTempPath(), "tempie" + ext);
using (var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
stream.CopyTo(sw);
}
stream.Dispose();
var startInfo = new ProcessStartInfo(myTempFile)
{
UseShellExecute = true
UseShellExecute = true
};
Process.Start(startInfo);
}
return;
}
Entries.Clear();
_selectedEntry = value;
if (value == null) return;
foreach (var (_, entryCh) in value.Childs)
{
Entries.Add(entryCh);
}
foreach (var (_, entryCh) in value.Childs) Entries.Add(entryCh);
}
}
public ContentBrowserViewModel() : base()
protected override void InitialiseInDesignMode()
{
var a = new ContentEntry(this, "A:","A", "");
var b = new ContentEntry(this, "B","B", "");
var a = new ContentEntry(this, "A:", "A", "");
var b = new ContentEntry(this, "B", "B", "");
a.TryAddChild(b);
Entries.Add(a);
Entries.Add(a);
}
public ContentBrowserViewModel(IServiceProvider provider, ContentService contentService, CancellationService cancellationService,
FileService fileService, HubService hubService, DebugService debugService, PopupMessageService popupService) : base(provider)
protected override void Initialise()
{
_provider = provider;
_contentService = contentService;
_cancellationService = cancellationService;
_fileService = fileService;
_debugService = debugService;
_popupService = popupService;
FillRoot(hubService.ServerList);
FillRoot(HubService.ServerList);
hubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
hubService.HubServerLoaded += GoHome;
if (!hubService.IsUpdating)
{
GoHome();
}
HubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
HubService.HubServerLoaded += GoHome;
if (!HubService.IsUpdating) GoHome();
}
private void GoHome()
{
SelectedEntry = null;
foreach (var entry in _root)
{
Entries.Add(entry);
}
foreach (var entry in _root) Entries.Add(entry);
}
private void HubServerChangedEventArgs(HubServerChangedEventArgs obj)
{
if(obj.Action == HubServerChangeAction.Clear) _root.Clear();
if (obj.Action == HubServerChangeAction.Add)
{
FillRoot(obj.Items);
};
if (obj.Action == HubServerChangeAction.Clear) _root.Clear();
if (obj.Action == HubServerChangeAction.Add) FillRoot(obj.Items);
}
private void FillRoot(IEnumerable<ServerHubInfo> infos)
{
foreach (var info in infos)
{
_root.Add(new ContentEntry(this, info.StatusData.Name,info.Address,info.Address));
}
foreach (var info in infos) _root.Add(new ContentEntry(this, info.StatusData.Name, info.Address, info.Address));
}
public async void Go(ContentPath path, bool appendHistory = true)
@@ -145,42 +126,34 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase
ServerText = path.Pathes[0];
path = new ContentPath("");
}
if (string.IsNullOrEmpty(ServerText))
{
SearchText = "";
GoHome();
return;
}
if (ServerText != SelectedEntry?.ServerName)
{
SelectedEntry = await CreateEntry(ServerText);
}
_debugService.Debug("Going to:" + path.Path);
if (ServerText != SelectedEntry?.ServerName) SelectedEntry = await CreateEntry(ServerText);
DebugService.Debug("Going to:" + path.Path);
var oriPath = path.Clone();
try
{
if (SelectedEntry == null || !SelectedEntry.GetRoot().TryGetEntry(path, out var centry))
{
throw new Exception("Not found! " + oriPath.Path);
}
if (appendHistory)
{
AppendHistory(SearchText);
}
if (appendHistory) AppendHistory(SearchText);
SearchText = oriPath.Path;
SelectedEntry = centry;
}
catch (Exception e)
{
Console.WriteLine(e);
SearchText = oriPath.Path;
_popupService.Popup(e);
PopupService.Popup(e);
}
}
@@ -189,7 +162,7 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase
{
Go(new ContentPath(GetHistory()), false);
}
public void OnGoEnter()
{
Go(new ContentPath(SearchText));
@@ -198,21 +171,21 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase
private async Task<ContentEntry> CreateEntry(string serverUrl)
{
var rurl = serverUrl.ToRobustUrl();
var info = await _contentService.GetBuildInfo(rurl, _cancellationService.Token);
var loading = _provider.GetService<LoadingContextViewModel>()!;
var info = await ContentService.GetBuildInfo(rurl, CancellationService.Token);
var loading = ViewHelperService.GetViewModel<LoadingContextViewModel>();
loading.LoadingName = "Loading entry";
_popupService.Popup(loading);
var items = await _contentService.EnsureItems(info.RobustManifestInfo, loading,
_cancellationService.Token);
PopupService.Popup(loading);
var items = await ContentService.EnsureItems(info.RobustManifestInfo, loading,
CancellationService.Token);
var rootEntry = new ContentEntry(this,"","", serverUrl);
var rootEntry = new ContentEntry(this, "", "", serverUrl);
foreach (var item in items)
{
var path = new ContentPath(item.Path);
rootEntry.CreateItem(path, item);
}
loading.Dispose();
return rootEntry;
@@ -220,7 +193,7 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase
private void AppendHistory(string str)
{
if(_history.Count >= 10) _history.RemoveAt(9);
if (_history.Count >= 10) _history.RemoveAt(9);
_history.Insert(0, str);
}
@@ -235,41 +208,13 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase
public class ContentEntry
{
private readonly ContentBrowserViewModel _viewModel;
public static IImage DirImage = new Bitmap(AssetLoader.Open(new Uri("avares://Nebula.Launcher/Assets/dir.png")));
public static IImage IconImage = new Bitmap(AssetLoader.Open(new Uri("avares://Nebula.Launcher/Assets/file.png")));
public RobustManifestItem? Item;
public bool IsDirectory => Item == null;
public string Name { get; private set; }
public string PathName { get; private set; }
public string ServerName { get; private set; }
public IImage IconPath { get; set; } = DirImage;
public ContentEntry? Parent { get; private set; }
public bool IsRoot => Parent == null;
private readonly Dictionary<string, ContentEntry> _childs = new();
private readonly ContentBrowserViewModel _viewModel;
public IReadOnlyDictionary<string, ContentEntry> Childs => _childs.ToFrozenDictionary();
public bool TryGetChild(string name,[NotNullWhen(true)] out ContentEntry? child)
{
return _childs.TryGetValue(name, out child);
}
public bool TryAddChild(ContentEntry contentEntry)
{
if(_childs.TryAdd(contentEntry.PathName, contentEntry))
{
contentEntry.Parent = this;
return true;
}
return false;
}
public RobustManifestItem? Item;
internal ContentEntry(ContentBrowserViewModel viewModel, string name, string pathName, string serverName)
{
@@ -279,6 +224,34 @@ public class ContentEntry
_viewModel = viewModel;
}
public bool IsDirectory => Item == null;
public string Name { get; private set; }
public string PathName { get; }
public string ServerName { get; }
public IImage IconPath { get; set; } = DirImage;
public ContentEntry? Parent { get; private set; }
public bool IsRoot => Parent == null;
public IReadOnlyDictionary<string, ContentEntry> Childs => _childs.ToFrozenDictionary();
public bool TryGetChild(string name, [NotNullWhen(true)] out ContentEntry? child)
{
return _childs.TryGetValue(name, out child);
}
public bool TryAddChild(ContentEntry contentEntry)
{
if (_childs.TryAdd(contentEntry.PathName, contentEntry))
{
contentEntry.Parent = this;
return true;
}
return false;
}
public ContentPath GetPath()
{
if (Parent != null)
@@ -287,6 +260,7 @@ public class ContentEntry
path.Pathes.Add(PathName);
return path;
}
return new ContentPath([PathName]);
}
@@ -295,8 +269,8 @@ public class ContentEntry
if (rootPath.Pathes.Count == 0) return this;
var fName = rootPath.GetNext();
if(!TryGetChild(fName, out var child))
if (!TryGetChild(fName, out var child))
{
child = new ContentEntry(_viewModel, fName, fName, ServerName);
TryAddChild(child);
@@ -321,7 +295,7 @@ public class ContentEntry
{
Item = item
};
dirEntry.TryAddChild(entry);
entry.IconPath = IconImage;
return entry;
@@ -330,7 +304,7 @@ public class ContentEntry
public bool TryGetEntry(ContentPath path, out ContentEntry? entry)
{
entry = null;
if (path.Pathes.Count == 0)
{
entry = this;
@@ -338,11 +312,8 @@ public class ContentEntry
}
var fName = path.GetNext();
if(!TryGetChild(fName, out var child))
{
return false;
}
if (!TryGetChild(fName, out var child)) return false;
return child.TryGetEntry(path, out entry);
}
@@ -353,14 +324,13 @@ public class ContentEntry
}
}
public struct ContentPath
{
public List<string> Pathes { get; private set; }
public List<string> Pathes { get; }
public ContentPath(List<string> pathes)
{
Pathes = pathes ?? new List<string>();
Pathes = pathes;
}
public ContentPath(string path)

View File

@@ -1,13 +1,17 @@
using System;
using System.Collections.ObjectModel;
using Nebula.Shared.Models;
namespace Nebula.Launcher.ViewModels;
namespace Nebula.Launcher.ViewModels.Pages;
public class FavoriteServerListViewModel : ViewModelBase
{
public FavoriteServerListViewModel() : base(){}
public FavoriteServerListViewModel(IServiceProvider provider) : base(provider){}
public ObservableCollection<ServerHubInfo> Servers = new();
protected override void Initialise()
{
}
protected override void InitialiseInDesignMode()
{
}
}

View File

@@ -4,50 +4,42 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.Services;
using Nebula.Launcher.Views.Pages;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ViewModels;
namespace Nebula.Launcher.ViewModels.Pages;
[ViewModelRegister(typeof(ServerListView))]
[ConstructGenerator]
public partial class ServerListViewModel : ViewModelBase
{
private readonly IServiceProvider _serviceProvider;
private readonly HubService _hubService;
public ObservableCollection<ServerEntryModelView> ServerInfos { get; } = new();
[ObservableProperty] private string _searchText = string.Empty;
public Action? OnSearchChange;
[ObservableProperty] private string _searchText;
[GenerateProperty] private HubService HubService { get; } = default!;
[GenerateProperty] private ViewHelperService ViewHelperService { get; } = default!;
public ObservableCollection<ServerEntryModelView> ServerInfos { get; } = new();
private List<ServerHubInfo> UnsortedServers { get; } = new();
//Design think
public ServerListViewModel()
protected override void InitialiseInDesignMode()
{
ServerInfos.Add(CreateServerView(new ServerHubInfo("ss14://localhost",new ServerStatus("Nebula","TestCraft", ["16+","RU"], "super", 12,55,1,false,DateTime.Now, 20),[])));
ServerInfos.Add(CreateServerView(new ServerHubInfo("ss14://localhost",
new ServerStatus("Nebula", "TestCraft", ["16+", "RU"], "super", 12, 55, 1, false, DateTime.Now, 20), [])));
}
//real think
public ServerListViewModel(IServiceProvider serviceProvider, HubService hubService) : base(serviceProvider)
protected override void Initialise()
{
_serviceProvider = serviceProvider;
_hubService = hubService;
foreach (var info in _hubService.ServerList)
{
UnsortedServers.Add(info);
}
hubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
hubService.HubServerLoaded += HubServerLoaded;
foreach (var info in HubService.ServerList) UnsortedServers.Add(info);
HubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
HubService.HubServerLoaded += HubServerLoaded;
OnSearchChange += OnChangeSearch;
if (!hubService.IsUpdating)
{
SortServers();
}
if (!HubService.IsUpdating) SortServers();
}
private void HubServerLoaded()
@@ -63,23 +55,12 @@ public partial class ServerListViewModel : ViewModelBase
private void HubServerChangedEventArgs(HubServerChangedEventArgs obj)
{
if (obj.Action == HubServerChangeAction.Add)
{
foreach (var info in obj.Items)
{
UnsortedServers.Add(info);
}
}
if(obj.Action == HubServerChangeAction.Remove)
{
if (obj.Action == HubServerChangeAction.Remove)
foreach (var info in obj.Items)
{
UnsortedServers.Remove(info);
}
}
if(obj.Action == HubServerChangeAction.Clear)
{
UnsortedServers.Clear();
}
if (obj.Action == HubServerChangeAction.Clear) UnsortedServers.Clear();
}
private void SortServers()
@@ -88,10 +69,7 @@ public partial class ServerListViewModel : ViewModelBase
{
ServerInfos.Clear();
UnsortedServers.Sort(new ServerComparer());
foreach (var server in UnsortedServers.Where(CheckServerThink))
{
ServerInfos.Add(CreateServerView(server));
}
foreach (var server in UnsortedServers.Where(CheckServerThink)) ServerInfos.Add(CreateServerView(server));
});
}
@@ -103,19 +81,18 @@ public partial class ServerListViewModel : ViewModelBase
private ServerEntryModelView CreateServerView(ServerHubInfo serverHubInfo)
{
var svn = GetViewModel<ServerEntryModelView>();
var svn = ViewHelperService.GetViewModel<ServerEntryModelView>();
svn.ServerHubInfo = serverHubInfo;
return svn;
}
public void FilterRequired()
{
}
public void UpdateRequired()
{
Task.Run(_hubService.UpdateHub);
Task.Run(HubService.UpdateHub);
}
}
@@ -131,6 +108,5 @@ public class ServerComparer : IComparer<ServerHubInfo>
return -1;
return y.StatusData.Players.CompareTo(x.StatusData.Players);
}
}

View File

@@ -1,31 +1,34 @@
using System;
using System.Collections.ObjectModel;
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.Views.Popup;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ViewModels;
namespace Nebula.Launcher.ViewModels.Popup;
[ViewModelRegister(typeof(ExceptionView), false)]
public class ExceptionViewModel : PopupViewModelBase
[ConstructGenerator]
public sealed partial class ExceptionViewModel : PopupViewModelBase
{
public ExceptionViewModel() : base()
{
var e = new Exception("TEST");
AppendError(e);
}
public ExceptionViewModel(IServiceProvider serviceProvider) : base(serviceProvider){}
[GenerateProperty] public override PopupMessageService PopupMessageService { get; }
public override string Title => "Oopsie! Some shit is happened now!";
public override bool IsClosable => true;
public ObservableCollection<Exception> Errors { get; } = new();
protected override void Initialise()
{
}
protected override void InitialiseInDesignMode()
{
var e = new Exception("TEST");
AppendError(e);
}
public void AppendError(Exception exception)
{
Errors.Add(exception);
if(exception.InnerException != null)
if (exception.InnerException != null)
AppendError(exception.InnerException);
}
}
}

View File

@@ -0,0 +1,25 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Nebula.Launcher.Views.Popup;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ViewModels.Popup;
[ViewModelRegister(typeof(InfoPopupView), false)]
[ConstructGenerator]
public partial class InfoPopupViewModel : PopupViewModelBase
{
[GenerateProperty] public override PopupMessageService PopupMessageService { get; }
[ObservableProperty] private string _infoText = "Test";
public override string Title => "Info";
public override bool IsClosable => true;
protected override void Initialise()
{
}
protected override void InitialiseInDesignMode()
{
}
}

View File

@@ -1,27 +1,25 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.Views.Popup;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ViewModels;
namespace Nebula.Launcher.ViewModels.Popup;
[ViewModelRegister(typeof(LoadingContextView), false)]
[ConstructGenerator]
public sealed partial class LoadingContextViewModel : PopupViewModelBase, ILoadingHandler
{
public LoadingContextViewModel() :base(){}
public LoadingContextViewModel(IServiceProvider provider) : base(provider){}
[GenerateProperty] public override PopupMessageService PopupMessageService { get; }
[ObservableProperty] private int _currJobs;
[ObservableProperty] private int _resolvedJobs;
public string LoadingName { get; set; } = "Loading...";
public override bool IsClosable => false;
public override string Title => LoadingName;
[ObservableProperty]
private int _currJobs;
[ObservableProperty]
private int _resolvedJobs;
public void SetJobsCount(int count)
{
CurrJobs = count;
@@ -35,11 +33,18 @@ public sealed partial class LoadingContextViewModel : PopupViewModelBase, ILoadi
public void SetResolvedJobsCount(int count)
{
ResolvedJobs = count;
}
public int GetResolvedJobsCount()
{
return ResolvedJobs;
}
protected override void Initialise()
{
}
protected override void InitialiseInDesignMode()
{
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.ObjectModel;
using Nebula.Launcher.Views.Popup;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ViewModels.Popup;
[ViewModelRegister(typeof(LogPopupView), false)]
[ConstructGenerator]
public sealed partial class LogPopupModelView : PopupViewModelBase
{
[GenerateProperty] public override PopupMessageService PopupMessageService { get; }
public override string Title => "LOG";
public override bool IsClosable => true;
public ObservableCollection<LogInfo> Logs { get; } = new();
protected override void InitialiseInDesignMode()
{
Logs.Add(new LogInfo
{
Category = "DEBG", Message = "MEOW MEOW TEST"
});
Logs.Add(new LogInfo
{
Category = "ERRO", Message = "MEOW MEOW TEST 11\naaaaa"
});
}
protected override void Initialise()
{
}
public void Append(string str)
{
Logs.Add(LogInfo.FromString(str));
}
}

View File

@@ -0,0 +1,17 @@
using System;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ViewModels.Popup;
public abstract class PopupViewModelBase : ViewModelBase, IDisposable
{
public abstract PopupMessageService PopupMessageService { get; }
public abstract string Title { get; }
public abstract bool IsClosable { get; }
public void Dispose()
{
PopupMessageService.ClosePopup(this);
}
}

View File

@@ -1,26 +0,0 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ViewModels;
public abstract class PopupViewModelBase : ViewModelBase, IDisposable
{
private readonly IServiceProvider _serviceProvider;
public abstract string Title { get; }
public abstract bool IsClosable { get; }
public PopupViewModelBase()
{
}
public PopupViewModelBase(IServiceProvider serviceProvider) : base(serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Dispose()
{
_serviceProvider.GetService<PopupMessageService>()?.ClosePopup(this);
}
}

View File

@@ -1,37 +1,40 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.Views.Popup;
using Nebula.Launcher.Services;
using Nebula.Launcher.ViewModels.Popup;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ViewModels;
[ViewModelRegister(isSingleton:false)]
[ViewModelRegister(isSingleton: false)]
[ConstructGenerator]
public partial class ServerEntryModelView : ViewModelBase
{
private readonly AuthService _authService = default!;
private readonly ContentService _contentService = default!;
private readonly CancellationService _cancellationService = default!;
private readonly DebugService _debugService = default!;
private readonly RunnerService _runnerService = default!;
private readonly PopupMessageService _popupMessageService;
private Process? _p;
public LogPopupModelView CurrLog;
[GenerateProperty] private AuthService AuthService { get; } = default!;
[GenerateProperty] private ContentService ContentService { get; } = default!;
[GenerateProperty] private CancellationService CancellationService { get; } = default!;
[GenerateProperty] private DebugService DebugService { get; } = default!;
[GenerateProperty] private RunnerService RunnerService { get; } = default!;
[GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!;
[GenerateProperty] private ViewHelperService ViewHelperService { get; } = default!;
public bool RunVisible => Process == null;
public ServerHubInfo ServerHubInfo { get; set; } = default!;
private Process? _p;
private Process? Process
{
get { return _p; }
get => _p;
set
{
_p = value;
@@ -39,32 +42,14 @@ public partial class ServerEntryModelView : ViewModelBase
}
}
public LogPopupModelView CurrLog;
public ServerEntryModelView() : base()
protected override void InitialiseInDesignMode()
{
CurrLog = GetViewModel<LogPopupModelView>();
CurrLog = ViewHelperService.GetViewModel<LogPopupModelView>();
}
public ServerEntryModelView(
IServiceProvider serviceProvider,
AuthService authService,
ContentService contentService,
CancellationService cancellationService,
DebugService debugService,
RunnerService runnerService, PopupMessageService popupMessageService
) : base(serviceProvider)
protected override void Initialise()
{
_authService = authService;
_contentService = contentService;
_cancellationService = cancellationService;
_debugService = debugService;
_runnerService = runnerService;
_popupMessageService = popupMessageService;
CurrLog = GetViewModel<LogPopupModelView>();
CurrLog = ViewHelperService.GetViewModel<LogPopupModelView>();
}
public void RunInstance()
@@ -74,84 +59,77 @@ public partial class ServerEntryModelView : ViewModelBase
public async Task RunAsync()
{
try
{
var authProv = _authService.SelectedAuth;
try
{
var authProv = AuthService.SelectedAuth;
var buildInfo =
await _contentService.GetBuildInfo(new RobustUrl(ServerHubInfo.Address), _cancellationService.Token);
using (var loadingContext = GetViewModel<LoadingContextViewModel>())
{
loadingContext.LoadingName = "Loading instance...";
((ILoadingHandler)loadingContext).AppendJob();
_popupMessageService.Popup(loadingContext);
await _runnerService.PrepareRun(buildInfo, loadingContext, _cancellationService.Token);
var buildInfo =
await ContentService.GetBuildInfo(new RobustUrl(ServerHubInfo.Address), CancellationService.Token);
Process = Process.Start(new ProcessStartInfo()
{
FileName = "dotnet.exe",
Arguments = "./Nebula.Runner.dll",
Environment =
{
{ "ROBUST_AUTH_USERID", authProv?.UserId.ToString() },
{ "ROBUST_AUTH_TOKEN", authProv?.Token.Token },
{ "ROBUST_AUTH_SERVER", authProv?.AuthLoginPassword.AuthServer },
{ "ROBUST_AUTH_PUBKEY", buildInfo.BuildInfo.Auth.PublicKey },
{ "GAME_URL", ServerHubInfo.Address },
{ "AUTH_LOGIN", authProv?.AuthLoginPassword.Login },
},
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = Encoding.UTF8
});
((ILoadingHandler)loadingContext).AppendResolvedJob();
}
if (Process is null)
{
return;
}
Process.EnableRaisingEvents = true;
Process.BeginOutputReadLine();
Process.BeginErrorReadLine();
using (var loadingContext = ViewHelperService.GetViewModel<LoadingContextViewModel>())
{
loadingContext.LoadingName = "Loading instance...";
((ILoadingHandler)loadingContext).AppendJob();
Process.OutputDataReceived += OnOutputDataReceived;
Process.ErrorDataReceived += OnErrorDataReceived;
PopupMessageService.Popup(loadingContext);
Process.Exited += OnExited;
}
catch (TaskCanceledException e)
{
_popupMessageService.Popup("Task canceled");
}
catch (Exception e)
{
_popupMessageService.Popup(e);
}
await RunnerService.PrepareRun(buildInfo, loadingContext, CancellationService.Token);
Process = Process.Start(new ProcessStartInfo
{
FileName = "dotnet.exe",
Arguments = "./Nebula.Runner.dll",
Environment =
{
{ "ROBUST_AUTH_USERID", authProv?.UserId.ToString() },
{ "ROBUST_AUTH_TOKEN", authProv?.Token.Token },
{ "ROBUST_AUTH_SERVER", authProv?.AuthLoginPassword.AuthServer },
{ "ROBUST_AUTH_PUBKEY", buildInfo.BuildInfo.Auth.PublicKey },
{ "GAME_URL", ServerHubInfo.Address },
{ "AUTH_LOGIN", authProv?.AuthLoginPassword.Login }
},
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = Encoding.UTF8
});
((ILoadingHandler)loadingContext).AppendResolvedJob();
}
if (Process is null) return;
Process.EnableRaisingEvents = true;
Process.BeginOutputReadLine();
Process.BeginErrorReadLine();
Process.OutputDataReceived += OnOutputDataReceived;
Process.ErrorDataReceived += OnErrorDataReceived;
Process.Exited += OnExited;
}
catch (TaskCanceledException _)
{
PopupMessageService.Popup("Task canceled");
}
catch (Exception e)
{
PopupMessageService.Popup(e);
}
}
private void OnExited(object? sender, EventArgs e)
{
if (Process is null)
{
return;
}
if (Process is null) return;
Process.OutputDataReceived -= OnOutputDataReceived;
Process.ErrorDataReceived -= OnErrorDataReceived;
Process.Exited -= OnExited;
_debugService.Log("PROCESS EXIT WITH CODE " + Process.ExitCode);
DebugService.Log("PROCESS EXIT WITH CODE " + Process.ExitCode);
Process.Dispose();
Process = null;
}
@@ -160,7 +138,7 @@ public partial class ServerEntryModelView : ViewModelBase
{
if (e.Data != null)
{
_debugService.Error(e.Data);
DebugService.Error(e.Data);
CurrLog.Append(e.Data);
}
}
@@ -169,7 +147,7 @@ public partial class ServerEntryModelView : ViewModelBase
{
if (e.Data != null)
{
_debugService.Log(e.Data);
DebugService.Log(e.Data);
CurrLog.Append(e.Data);
}
}
@@ -177,29 +155,24 @@ public partial class ServerEntryModelView : ViewModelBase
public void ReadLog()
{
_popupMessageService.Popup(CurrLog);
PopupMessageService.Popup(CurrLog);
}
public void StopInstance()
{
Process?.CloseMainWindow();
}
static string FindDotnetPath()
private static string FindDotnetPath()
{
var pathEnv = Environment.GetEnvironmentVariable("PATH");
var paths = pathEnv?.Split(System.IO.Path.PathSeparator);
var paths = pathEnv?.Split(Path.PathSeparator);
if (paths != null)
{
foreach (var path in paths)
{
var dotnetPath = System.IO.Path.Combine(path, "dotnet");
if (System.IO.File.Exists(dotnetPath))
{
return dotnetPath;
}
var dotnetPath = Path.Combine(path, "dotnet");
if (File.Exists(dotnetPath)) return dotnetPath;
}
}
throw new Exception("Dotnet not found!");
}
@@ -207,19 +180,16 @@ public partial class ServerEntryModelView : ViewModelBase
public sealed class LogInfo
{
public string Category { get; set; } = "LOG";
public string Category { get; set; } = "LOG";
public IBrush CategoryColor { get; set; } = Brush.Parse("#424242");
public string Message { get; set; } = "";
public static LogInfo FromString(string input)
{
var matches = Regex.Matches(input, @"(\[(?<c>.*)\] (?<m>.*))|(?<m>.*)");
string category = "All";
var category = "All";
if( matches[0].Groups.TryGetValue("c", out var c))
{
category = c.Value;
}
if (matches[0].Groups.TryGetValue("c", out var c)) category = c.Value;
var color = Brush.Parse("#444444");
@@ -235,42 +205,11 @@ public sealed class LogInfo
color = Brush.Parse("#0ab3c9");
break;
}
var message = matches[0].Groups["m"].Value;
return new LogInfo()
return new LogInfo
{
Category = category, Message = message, CategoryColor = color
};
}
}
[ViewModelRegister(typeof(LogPopupView), false)]
public sealed class LogPopupModelView : PopupViewModelBase
{
public LogPopupModelView() : base()
{
Logs.Add(new LogInfo()
{
Category = "DEBG", Message = "MEOW MEOW TEST"
});
Logs.Add(new LogInfo()
{
Category = "ERRO", Message = "MEOW MEOW TEST 11\naaaaa"
});
}
public LogPopupModelView(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
public override string Title => "LOG";
public override bool IsClosable => true;
public ObservableCollection<LogInfo> Logs { get; } = new();
public void Append(string str)
{
Logs.Add(LogInfo.FromString(str));
}
}

View File

@@ -1,51 +1,9 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace Nebula.Launcher.ViewModels;
public abstract class ViewModelBase : ObservableObject
{
private readonly IServiceProvider _serviceProvider;
public ViewModelBase()
{
AssertDesignMode();
_serviceProvider = default!;
}
public ViewModelBase(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public bool TryGetViewModel(Type type,[NotNullWhen(true)] out ViewModelBase? viewModelBase)
{
viewModelBase = null;
var vm = Design.IsDesignMode
? Activator.CreateInstance(type)
: _serviceProvider.GetService(type);
if (vm is not ViewModelBase vmb) return false;
viewModelBase = vmb;
return true;
}
public bool TryGetViewModel<T>([NotNullWhen(true)] out T? viewModelBase) where T: ViewModelBase
{
var success = TryGetViewModel(typeof(T), out var vmb);
viewModelBase = (T?)vmb;
return success;
}
public T GetViewModel<T>() where T: ViewModelBase
{
TryGetViewModel<T>(out var viewModelBase);
return viewModelBase!;
}
public void AssertDesignMode()
{
if (!Design.IsDesignMode) throw new Exception();
}
protected abstract void InitialiseInDesignMode();
protected abstract void Initialise();
}

View File

@@ -85,7 +85,8 @@
Padding="5">
<Label FontSize="10" Foreground="#777777">
<Panel>
<Button Command="{Binding OpenLink}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0" Padding="0" CornerRadius="0" Background="#00000000">
<Button Command="{Binding OpenLink}" HorizontalAlignment="Left" VerticalAlignment="Center"
Margin="0" Padding="0" CornerRadius="0" Background="#00000000">
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center">https://cinka.ru/nebula-launcher/</TextBlock>
</Button>
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Center">prototype-product-v0.01</TextBlock>
@@ -141,4 +142,4 @@
</Border>
</Panel>
</Panel>
</UserControl>
</UserControl>

View File

@@ -1,15 +1,10 @@
using System;
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Nebula.Launcher.ViewModels;
namespace Nebula.Launcher.Views;
public partial class MainView : UserControl
{
// This constructor is used when the view is created by the XAML Previewer
public MainView()
{

View File

@@ -12,4 +12,4 @@
xmlns="https://github.com/avaloniaui"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />

View File

@@ -3,14 +3,14 @@
d:DesignWidth="800"
mc:Ignorable="d"
x:Class="Nebula.Launcher.Views.Pages.AccountInfoView"
x:DataType="viewModels:AccountInfoViewModel"
x:DataType="pages:AccountInfoViewModel"
xmlns="https://github.com/avaloniaui"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:Nebula.Launcher.ViewModels.Pages">
<Design.DataContext>
<viewModels:AccountInfoViewModel />
<pages:AccountInfoViewModel />
</Design.DataContext>
<Grid
ColumnDefinitions="*,1.5*"
@@ -29,7 +29,7 @@
ItemsSource="{Binding Accounts}"
Padding="0">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:AuthLoginPasswordModel}">
<DataTemplate DataType="{x:Type pages:AuthLoginPasswordModel}">
<Border
CornerRadius="0,10,0,10"
Margin="5,5,5,0"
@@ -168,4 +168,4 @@
</Border>
</StackPanel>
</Grid>
</UserControl>
</UserControl>

View File

@@ -1,16 +1,17 @@
using Avalonia.Controls;
using Nebula.Launcher.ViewModels;
using AccountInfoViewModel = Nebula.Launcher.ViewModels.Pages.AccountInfoViewModel;
namespace Nebula.Launcher.Views.Pages;
public interface ITab;
public partial class AccountInfoView : UserControl
{
public AccountInfoView()
{
InitializeComponent();
}
public AccountInfoView(AccountInfoViewModel viewModel)
: this()
{

View File

@@ -3,14 +3,14 @@
d:DesignWidth="800"
mc:Ignorable="d"
x:Class="Nebula.Launcher.Views.Pages.ContentBrowserView"
x:DataType="viewModels:ContentBrowserViewModel"
x:DataType="pages:ContentBrowserViewModel"
xmlns="https://github.com/avaloniaui"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:Nebula.Launcher.ViewModels.Pages">
<Design.DataContext>
<viewModels:ContentBrowserViewModel />
<pages:ContentBrowserViewModel />
</Design.DataContext>
<Grid
@@ -60,7 +60,7 @@
ItemsSource="{Binding Entries}"
Padding="0,0,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:ContentEntry}">
<DataTemplate DataType="{x:Type pages:ContentEntry}">
<Button
Command="{Binding OnPathGo}"
CornerRadius="0"
@@ -86,4 +86,4 @@
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
</UserControl>

View File

@@ -1,7 +1,5 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Nebula.Launcher.ViewModels;
using ContentBrowserViewModel = Nebula.Launcher.ViewModels.Pages.ContentBrowserViewModel;
namespace Nebula.Launcher.Views.Pages;
@@ -11,7 +9,7 @@ public partial class ContentBrowserView : UserControl
{
InitializeComponent();
}
public ContentBrowserView(ContentBrowserViewModel viewModel)
: this()
{

View File

@@ -8,4 +8,4 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
Welcome to Avalonia!
</UserControl>
</UserControl>

View File

@@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Nebula.Launcher.Views.Pages;

View File

@@ -3,16 +3,17 @@
d:DesignWidth="800"
mc:Ignorable="d"
x:Class="Nebula.Launcher.Views.Pages.ServerListView"
x:DataType="viewModels:ServerListViewModel"
x:DataType="pages:ServerListViewModel"
xmlns="https://github.com/avaloniaui"
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:Nebula.Launcher.ViewModels.Pages">
<Design.DataContext>
<viewModels:ServerListViewModel />
<pages:ServerListViewModel />
</Design.DataContext>
<Grid
@@ -67,7 +68,8 @@
Grid.Row="1"
MinHeight="50">
<Border.Background>
<ImageBrush Stretch="UniformToFill" asyncImageLoader:ImageBrushLoader.Source="https://t4.ftcdn.net/jpg/00/81/55/69/360_F_81556974_8sF8cKszJaRfBGd5sDt1RXE2QbzDtQqs.jpg" />
<ImageBrush Stretch="UniformToFill"
asyncImageLoader:ImageBrushLoader.Source="https://t4.ftcdn.net/jpg/00/81/55/69/360_F_81556974_8sF8cKszJaRfBGd5sDt1RXE2QbzDtQqs.jpg" />
</Border.Background>
<Border
BorderThickness="0,2,2,0"
@@ -189,4 +191,4 @@
</Grid>
</Border>
</Grid>
</UserControl>
</UserControl>

View File

@@ -1,5 +1,5 @@
using Avalonia.Controls;
using Nebula.Launcher.ViewModels;
using ServerListViewModel = Nebula.Launcher.ViewModels.Pages.ServerListViewModel;
namespace Nebula.Launcher.Views.Pages;

View File

@@ -2,13 +2,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:popup="clr-namespace:Nebula.Launcher.ViewModels.Popup"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Nebula.Launcher.Views.Popup.ExceptionView"
x:DataType="viewModels:ExceptionViewModel">
x:DataType="popup:ExceptionViewModel">
<Design.DataContext>
<viewModels:ExceptionViewModel />
<popup:ExceptionViewModel />
</Design.DataContext>
<ScrollViewer Margin="10" Padding="0,0,8,0">
<ItemsControl
@@ -20,13 +20,17 @@
<Border Background="#333333" CornerRadius="5" Margin="0,0,0,5">
<StackPanel>
<Border Background="#aa2222" CornerRadius="5,5,0,0">
<Label Margin="4"><TextBlock Text="{Binding Message}"/></Label>
<Label Margin="4">
<TextBlock Text="{Binding Message}" />
</Label>
</Border>
<Label Margin="4"><TextBlock Text="{Binding StackTrace}"/></Label>
<Label Margin="4">
<TextBlock Text="{Binding StackTrace}" />
</Label>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>
</UserControl>

View File

@@ -1,8 +1,5 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Nebula.Launcher.ViewModels;
using Nebula.Launcher.ViewModels.Popup;
namespace Nebula.Launcher.Views.Popup;

View File

@@ -3,14 +3,14 @@
d:DesignWidth="800"
mc:Ignorable="d"
x:Class="Nebula.Launcher.Views.Popup.InfoPopupView"
x:DataType="viewModels:InfoPopupViewModel"
x:DataType="popup:InfoPopupViewModel"
xmlns="https://github.com/avaloniaui"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:popup="clr-namespace:Nebula.Launcher.ViewModels.Popup">
<Design.DataContext>
<viewModels:InfoPopupViewModel />
<popup:InfoPopupViewModel />
</Design.DataContext>
<Panel HorizontalAlignment="Center" VerticalAlignment="Center">
@@ -18,4 +18,4 @@
<TextBlock Text="{Binding InfoText}" />
</Label>
</Panel>
</UserControl>
</UserControl>

View File

@@ -1,7 +1,5 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Nebula.Launcher.ViewModels;
using InfoPopupViewModel = Nebula.Launcher.ViewModels.Popup.InfoPopupViewModel;
namespace Nebula.Launcher.Views.Popup;

View File

@@ -2,31 +2,31 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
xmlns:popup="clr-namespace:Nebula.Launcher.ViewModels.Popup"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Nebula.Launcher.Views.Popup.LoadingContextView"
x:DataType="viewModels:LoadingContextViewModel">
x:DataType="popup:LoadingContextViewModel">
<Design.DataContext>
<viewModels:LoadingContextViewModel />
<popup:LoadingContextViewModel />
</Design.DataContext>
<StackPanel Margin="25" Spacing="15">
<ProgressBar Height="40" Maximum="{Binding CurrJobs}" Value="{Binding ResolvedJobs}"/>
<ProgressBar Height="40" Maximum="{Binding CurrJobs}" Value="{Binding ResolvedJobs}" />
<Panel>
<StackPanel Orientation="Horizontal" Spacing="5" HorizontalAlignment="Left" VerticalAlignment="Center">
<Label>
<TextBlock Text="{Binding ResolvedJobs}"/>
<TextBlock Text="{Binding ResolvedJobs}" />
</Label>
<Label>
/
</Label>
<Label>
<TextBlock Text="{Binding CurrJobs}"/>
<TextBlock Text="{Binding CurrJobs}" />
</Label>
</StackPanel>
<Button HorizontalAlignment="Right" VerticalAlignment="Center">
<Label>Cancel</Label>
</Button>
</Panel>
</StackPanel>
</UserControl>
</UserControl>

View File

@@ -1,5 +1,5 @@
using Avalonia.Controls;
using Nebula.Launcher.ViewModels;
using LoadingContextViewModel = Nebula.Launcher.ViewModels.Popup.LoadingContextViewModel;
namespace Nebula.Launcher.Views.Popup;
@@ -9,8 +9,8 @@ public partial class LoadingContextView : UserControl
{
InitializeComponent();
}
public LoadingContextView(LoadingContextViewModel viewModel): this()
public LoadingContextView(LoadingContextViewModel viewModel) : this()
{
DataContext = viewModel;
}

View File

@@ -3,13 +3,14 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
xmlns:popup="clr-namespace:Nebula.Launcher.ViewModels.Popup"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Nebula.Launcher.Views.Popup.LogPopupView"
x:DataType="viewModels:LogPopupModelView">
x:DataType="popup:LogPopupModelView">
<Design.DataContext>
<viewModels:LogPopupModelView />
<popup:LogPopupModelView />
</Design.DataContext>
<ScrollViewer Margin="10" Padding="0,0,8,0">
<ItemsControl
Background="#00000000"
@@ -20,20 +21,20 @@
<Border CornerRadius="5" Margin="0,0,0,5">
<StackPanel Orientation="Horizontal" Spacing="5" Margin="0">
<Border MinWidth="100"
Background="{Binding CategoryColor}"
CornerRadius="5,0,0,5"
Padding="10,0,12,0" BorderThickness="2,0,2,0">
<Label FontSize="15" VerticalAlignment="Center">
<TextBlock Text="{Binding Category }"></TextBlock>
Background="{Binding CategoryColor}"
CornerRadius="5,0,0,5"
Padding="10,0,12,0" BorderThickness="2,0,2,0">
<Label FontSize="15" VerticalAlignment="Center">
<TextBlock Text="{Binding Category }" />
</Label>
</Border>
<Label FontSize="12" VerticalAlignment="Center">
<TextBlock Text="{Binding Message }"></TextBlock>
<TextBlock Text="{Binding Message }" />
</Label>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>

View File

@@ -1,7 +1,5 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Nebula.Launcher.ViewModels;
using Nebula.Launcher.ViewModels.Popup;
namespace Nebula.Launcher.Views.Popup;

View File

@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="Nebula.Launcher.Desktop"/>
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="Nebula.Launcher.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

View File

@@ -7,12 +7,17 @@ using Robust.LoaderApi;
namespace Nebula.Runner;
[ServiceRegister]
public sealed class App(DebugService debugService, RunnerService runnerService, ContentService contentService) : IRedialApi
public sealed class App(DebugService debugService, RunnerService runnerService, ContentService contentService)
: IRedialApi
{
public void Redial(Uri uri, string text = "")
{
}
public async Task Run(string[] args1)
{
debugService.Log("HELLO!!! ");
var login = Environment.GetEnvironmentVariable("AUTH_LOGIN") ?? "Alexandra";
var urlraw = Environment.GetEnvironmentVariable("GAME_URL") ?? "ss14://localhost";
@@ -20,7 +25,7 @@ public sealed class App(DebugService debugService, RunnerService runnerService,
using var cancelTokenSource = new CancellationTokenSource();
var buildInfo = await contentService.GetBuildInfo(url, cancelTokenSource.Token);
var args = new List<string>
{
@@ -46,27 +51,22 @@ public sealed class App(DebugService debugService, RunnerService runnerService,
args.Add("--ss14-address");
args.Add(url.ToString());
await runnerService.Run(args.ToArray(), buildInfo, this, new ConsoleLoadingHandler(), cancelTokenSource.Token);
}
public void Redial(Uri uri, string text = "")
{
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;
private float _percent = 0f;
public void SetJobsCount(int count)
{
_currJobs = count;
UpdatePercent();
Draw();
}
@@ -79,7 +79,7 @@ public sealed class ConsoleLoadingHandler : ILoadingHandler
public void SetResolvedJobsCount(int count)
{
_resolvedJobs = count;
UpdatePercent();
Draw();
}
@@ -91,15 +91,15 @@ public sealed class ConsoleLoadingHandler : ILoadingHandler
private void UpdatePercent()
{
if(_currJobs == 0)
if (_currJobs == 0)
{
_percent = 0;
return;
}
if(_resolvedJobs > _currJobs) return;
_percent = _resolvedJobs /(float) _currJobs;
if (_resolvedJobs > _currJobs) return;
_percent = _resolvedJobs / (float)_currJobs;
}
private void Draw()
@@ -107,20 +107,13 @@ public sealed class ConsoleLoadingHandler : ILoadingHandler
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(" ");
}
for (var i = 0; i < fullCount; i++) Console.Write("#");
for (var i = 0; i < emptyCount; i++) Console.Write(" ");
Console.Write($"\t {_resolvedJobs}/{_currJobs}");
}
}

View File

@@ -8,11 +8,11 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Nebula.Shared\Nebula.Shared.csproj" />
<ProjectReference Include="..\Nebula.Shared\Nebula.Shared.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0"/>
<PackageReference Include="SharpZstd.Interop" Version="1.5.6"/>
</ItemGroup>
</Project>

View File

@@ -1,4 +1,3 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Shared;
@@ -10,7 +9,7 @@ public static class Program
{
var services = new ServiceCollection();
services.AddServices();
var serviceProvider = services.BuildServiceProvider();
var task = serviceProvider.GetService<App>()!.Run(args);
task.Wait();

View File

@@ -4,22 +4,29 @@ namespace Nebula.Shared;
public static class CurrentConVar
{
public static readonly ConVar<string> EngineManifestUrl =
public static readonly ConVar<string> EngineManifestUrl =
ConVarBuilder.Build("engine.manifestUrl", "https://robust-builds.cdn.spacestation14.com/manifest.json");
public static readonly ConVar<string> EngineModuleManifestUrl =
public static readonly ConVar<string> EngineModuleManifestUrl =
ConVarBuilder.Build("engine.moduleManifestUrl", "https://robust-builds.cdn.spacestation14.com/modules.json");
public static readonly ConVar<int> ManifestDownloadProtocolVersion =
ConVarBuilder.Build("engine.manifestDownloadProtocolVersion", 1);
public static readonly ConVar<string> RobustAssemblyName =
public static readonly ConVar<string> RobustAssemblyName =
ConVarBuilder.Build("engine.robustAssemblyName", "Robust.Client");
public static readonly ConVar<string[]> Hub = ConVarBuilder.Build<string[]>("launcher.hub", [
"https://hub.spacestation14.com/api/servers"
]);
public static readonly ConVar<string[]> AuthServers = ConVarBuilder.Build<string[]>("launcher.authServers", [
"https://auth.spacestation14.com/"
]);
public static readonly ConVar<AuthLoginPassword[]> AuthProfiles = ConVarBuilder.Build<AuthLoginPassword[]>("auth.profiles", []);
public static readonly ConVar<AuthLoginPassword> AuthCurrent = ConVarBuilder.Build<AuthLoginPassword>("auth.current");
public static readonly ConVar<AuthLoginPassword[]> AuthProfiles =
ConVarBuilder.Build<AuthLoginPassword[]>("auth.profiles", []);
public static readonly ConVar<AuthLoginPassword> AuthCurrent =
ConVarBuilder.Build<AuthLoginPassword>("auth.current");
}

View File

@@ -10,7 +10,7 @@ public class AssemblyApi : IFileApi
{
_root = root;
}
public bool TryOpen(string path, out Stream? stream)
{
return _root.TryOpen(path, out stream);

View File

@@ -15,7 +15,6 @@ public sealed class FileApi : IReadWriteFileApi
{
var fullPath = Path.Join(RootPath, path);
if (File.Exists(fullPath))
{
try
{
stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
@@ -26,7 +25,6 @@ public sealed class FileApi : IReadWriteFileApi
stream = null;
return false;
}
}
stream = null;
return false;
@@ -66,6 +64,7 @@ public sealed class FileApi : IReadWriteFileApi
{
// Log exception if necessary
}
return false;
}

View File

@@ -4,7 +4,7 @@ public interface ILoadingHandler
{
public void SetJobsCount(int count);
public int GetJobsCount();
public void SetResolvedJobsCount(int count);
public int GetResolvedJobsCount();
@@ -30,7 +30,7 @@ public interface ILoadingHandler
}
}
public sealed class QueryJob: IDisposable
public sealed class QueryJob : IDisposable
{
private readonly ILoadingHandler _handler;
@@ -39,7 +39,7 @@ public sealed class QueryJob: IDisposable
_handler = handler;
handler.AppendJob();
}
public void Dispose()
{
_handler.AppendResolvedJob();

View File

@@ -1,2 +1,3 @@
namespace Nebula.Shared.Models;
public record ListItemTemplate(Type ModelType, string IconKey, string Label);

View File

@@ -4,18 +4,26 @@ namespace Nebula.Shared.Models;
public sealed record AuthInfo(
[property: JsonPropertyName("mode")] string Mode,
[property: JsonPropertyName("public_key")] string PublicKey);
[property: JsonPropertyName("public_key")]
string PublicKey);
public sealed record BuildInfo(
[property: JsonPropertyName("engine_version")] string EngineVersion,
[property: JsonPropertyName("fork_id")] string ForkId,
[property: JsonPropertyName("version")] string Version,
[property: JsonPropertyName("download_url")] string DownloadUrl,
[property: JsonPropertyName("manifest_download_url")] string ManifestDownloadUrl,
[property: JsonPropertyName("manifest_url")] string ManifestUrl,
[property: JsonPropertyName("engine_version")]
string EngineVersion,
[property: JsonPropertyName("fork_id")]
string ForkId,
[property: JsonPropertyName("version")]
string Version,
[property: JsonPropertyName("download_url")]
string DownloadUrl,
[property: JsonPropertyName("manifest_download_url")]
string ManifestDownloadUrl,
[property: JsonPropertyName("manifest_url")]
string ManifestUrl,
[property: JsonPropertyName("acz")] bool Acz,
[property: JsonPropertyName("hash")] string Hash,
[property: JsonPropertyName("manifest_hash")] string ManifestHash);
[property: JsonPropertyName("manifest_hash")]
string ManifestHash);
public sealed record ServerLink(
[property: JsonPropertyName("name")] string Name,
@@ -23,16 +31,20 @@ public sealed record ServerLink(
[property: JsonPropertyName("url")] string Url);
public sealed record ServerInfo(
[property: JsonPropertyName("connect_address")] string ConnectAddress,
[property: JsonPropertyName("connect_address")]
string ConnectAddress,
[property: JsonPropertyName("auth")] AuthInfo Auth,
[property: JsonPropertyName("build")] BuildInfo Build,
[property: JsonPropertyName("desc")] string Desc,
[property: JsonPropertyName("links")] List<ServerLink> Links);
public sealed record EngineVersionInfo(
[property: JsonPropertyName("insecure")] bool Insecure,
[property: JsonPropertyName("redirect")] string? RedirectVersion,
[property: JsonPropertyName("platforms")] Dictionary<string, EngineBuildInfo> Platforms);
[property: JsonPropertyName("insecure")]
bool Insecure,
[property: JsonPropertyName("redirect")]
string? RedirectVersion,
[property: JsonPropertyName("platforms")]
Dictionary<string, EngineBuildInfo> Platforms);
public sealed class EngineBuildInfo
{
@@ -47,27 +59,39 @@ public sealed class EngineBuildInfo
}
public sealed record ServerHubInfo(
[property: JsonPropertyName("address")] string Address,
[property: JsonPropertyName("statusData")] ServerStatus StatusData,
[property: JsonPropertyName("inferredTags")] List<string> InferredTags);
[property: JsonPropertyName("address")]
string Address,
[property: JsonPropertyName("statusData")]
ServerStatus StatusData,
[property: JsonPropertyName("inferredTags")]
List<string> InferredTags);
public sealed record ServerStatus(
[property: JsonPropertyName("map")] string Map,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("tags")] List<string> Tags,
[property: JsonPropertyName("preset")] string Preset,
[property: JsonPropertyName("players")] int Players,
[property: JsonPropertyName("round_id")] int RoundId,
[property: JsonPropertyName("run_level")] int RunLevel,
[property: JsonPropertyName("panic_bunker")] bool PanicBunker,
[property: JsonPropertyName("round_start_time")] DateTime? RoundStartTime,
[property: JsonPropertyName("soft_max_players")] int SoftMaxPlayers);
[property: JsonPropertyName("players")]
int Players,
[property: JsonPropertyName("round_id")]
int RoundId,
[property: JsonPropertyName("run_level")]
int RunLevel,
[property: JsonPropertyName("panic_bunker")]
bool PanicBunker,
[property: JsonPropertyName("round_start_time")]
DateTime? RoundStartTime,
[property: JsonPropertyName("soft_max_players")]
int SoftMaxPlayers);
public sealed record ModulesInfo(
[property: JsonPropertyName("modules")] Dictionary<string, Module> Modules);
[property: JsonPropertyName("modules")]
Dictionary<string, Module> Modules);
public sealed record Module(
[property: JsonPropertyName("versions")] Dictionary<string, ModuleVersionInfo> Versions);
[property: JsonPropertyName("versions")]
Dictionary<string, ModuleVersionInfo> Versions);
public sealed record ModuleVersionInfo(
[property: JsonPropertyName("platforms")] Dictionary<string, EngineBuildInfo> Platforms);
[property: JsonPropertyName("platforms")]
Dictionary<string, EngineBuildInfo> Platforms);

View File

@@ -11,13 +11,14 @@
<EmbeddedResource Include="Utils\runtime.json">
<LogicalName>Utility.runtime.json</LogicalName>
</EmbeddedResource>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Robust.Natives" Version="0.1.1" />
<PackageReference Include="SharpZstd.Interop" Version="1.5.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0"/>
<PackageReference Include="Robust.Natives" Version="0.1.1"/>
<PackageReference Include="SharpZstd.Interop" Version="1.5.6"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Robust.LoaderApi\Robust.LoaderApi\Robust.LoaderApi.csproj" />
<ProjectReference Include="..\Nebula.SourceGenerators\Nebula.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\Robust.LoaderApi\Robust.LoaderApi\Robust.LoaderApi.csproj"/>
</ItemGroup>
</Project>

View File

@@ -7,47 +7,39 @@ public static class ServiceExt
{
public static void AddServices(this IServiceCollection services)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
AddServices(services, assembly);
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) AddServices(services, assembly);
}
public static void AddServices(this IServiceCollection services, Assembly assembly)
{
foreach (var (type, inference) in GetServicesWithHelpAttribute(assembly))
{
Console.WriteLine("[ServiceMng] ADD SERVICE " + type);
if (inference is null)
{
services.AddSingleton(type);
}
else
{
services.AddSingleton(inference, type);
}
}
}
private static IEnumerable<(Type,Type?)> GetServicesWithHelpAttribute(Assembly assembly) {
foreach(Type type in assembly.GetTypes())
private static IEnumerable<(Type, Type?)> GetServicesWithHelpAttribute(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
{
var attr = type.GetCustomAttribute<ServiceRegisterAttribute>();
if (attr is not null) {
yield return (type, attr.Inference);
}
if (attr is not null) yield return (type, attr.Inference);
}
}
}
public sealed class ServiceRegisterAttribute : Attribute
{
public Type? Inference { get; }
public bool IsSingleton { get; }
public ServiceRegisterAttribute(Type? inference = null, bool isSingleton = true)
{
IsSingleton = isSingleton;
Inference = inference;
}
public Type? Inference { get; }
public bool IsSingleton { get; }
}

View File

@@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
using System.Runtime.Loader;
using Nebula.Shared.FileApis;
using Robust.LoaderApi;
using SharpZstd.Interop;
namespace Nebula.Shared.Services;
@@ -13,23 +14,23 @@ public class AssemblyService
private readonly List<Assembly> _assemblies = new();
private readonly DebugService _debugService;
private readonly HashSet<string> _resolvingAssemblies = new();
public AssemblyService(DebugService debugService)
{
_debugService = debugService;
SharpZstd.Interop.ZstdImportResolver.ResolveLibrary += (name, assembly1, path) =>
ZstdImportResolver.ResolveLibrary += (name, assembly1, path) =>
{
if (name.Equals("SharpZstd.Native"))
{
_debugService.Debug("RESOLVING SHARPZSTD THINK: " + name + " " + path);
GetRuntimeInfo(out string platform, out string architecture, out string extension);
string fileName = GetDllName(platform, architecture, extension);
GetRuntimeInfo(out var platform, out var architecture, out var extension);
var fileName = GetDllName(platform, architecture, extension);
if (NativeLibrary.TryLoad(fileName, assembly1, path, out var nativeLibrary))
{
return nativeLibrary;
}
if (NativeLibrary.TryLoad(fileName, assembly1, path, out var nativeLibrary)) return nativeLibrary;
}
return IntPtr.Zero;
};
}
@@ -98,8 +99,6 @@ public class AssemblyService
assemblyApi.TryOpen($"{name}.pdb", out pdb);
return true;
}
private readonly HashSet<string> _resolvingAssemblies = new HashSet<string>();
private Assembly? OnAssemblyResolving(AssemblyLoadContext context, AssemblyName name, AssemblyApi assemblyApi)
{
@@ -108,7 +107,7 @@ public class AssemblyService
_debugService.Debug($"Already resolving {name.Name}, skipping.");
return null; // Prevent recursive resolution
}
try
{
_resolvingAssemblies.Add(name.FullName);
@@ -130,18 +129,18 @@ public class AssemblyService
if (NativeLibrary.TryLoad(a, out var handle))
return handle;
_debugService.Error("Loading dll error! Not found");
return IntPtr.Zero;
}
public static string GetDllName(
string platform,
string architecture,
string extension)
{
string name = $"SharpZstd.Native.{extension}";
var name = $"SharpZstd.Native.{extension}";
return name;
}
@@ -172,24 +171,14 @@ public class AssemblyService
}
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
{
architecture = "x64";
}
else if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
{
architecture = "x86";
}
else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm)
{
architecture = "arm";
}
else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
architecture = "arm64";
}
else
{
throw new PlatformNotSupportedException("Unsupported process architecture.");
}
}
}

View File

@@ -4,37 +4,37 @@ using Nebula.Shared.Models.Auth;
namespace Nebula.Shared.Services;
[ServiceRegister]
public partial class AuthService(
RestService restService,
DebugService debugService,
public class AuthService(
RestService restService,
DebugService debugService,
CancellationService cancellationService)
{
private readonly HttpClient _httpClient = new();
public CurrentAuthInfo? SelectedAuth { get; internal set; }
public string Reason = "";
public CurrentAuthInfo? SelectedAuth { get; internal set; }
public async Task<bool> Auth(AuthLoginPassword authLoginPassword)
{
var authServer = authLoginPassword.AuthServer;
var login = authLoginPassword.Login;
var password = authLoginPassword.Password;
debugService.Debug($"Auth to {authServer}api/auth/authenticate {login}");
var authUrl = new Uri($"{authServer}api/auth/authenticate");
var result =
await restService.PostAsync<AuthenticateResponse, AuthenticateRequest>(
new AuthenticateRequest(login, password), authUrl, cancellationService.Token);
if (result.Value is null)
{
Reason = result.Message;
return false;
}
SelectedAuth = new CurrentAuthInfo(result.Value.UserId,
SelectedAuth = new CurrentAuthInfo(result.Value.UserId,
new LoginToken(result.Value.Token, result.Value.ExpireTime), authLoginPassword);
return true;
@@ -68,4 +68,5 @@ public partial class AuthService(
}
public sealed record CurrentAuthInfo(Guid UserId, LoginToken Token, AuthLoginPassword AuthLoginPassword);
public record AuthLoginPassword(string Login, string Password, string AuthServer);
public record AuthLoginPassword(string Login, string Password, string AuthServer);

View File

@@ -5,15 +5,15 @@ namespace Nebula.Shared.Services;
public class ConVar<T>
{
public string Name { get; }
public Type Type => typeof(T);
public T? DefaultValue { get; }
public ConVar(string name, T? defaultValue = default)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
DefaultValue = defaultValue;
}
public string Name { get; }
public Type Type => typeof(T);
public T? DefaultValue { get; }
}
public static class ConVarBuilder
@@ -30,8 +30,8 @@ public static class ConVarBuilder
[ServiceRegister]
public class ConfigurationService
{
private readonly FileService _fileService;
private readonly DebugService _debugService;
private readonly FileService _fileService;
public ConfigurationService(FileService fileService, DebugService debugService)
{
@@ -46,7 +46,6 @@ public class ConfigurationService
try
{
if (_fileService.ConfigurationApi.TryOpen(GetFileName(conVar), out var stream))
{
using (stream)
{
var obj = JsonSerializer.Deserialize<T>(stream);
@@ -56,7 +55,6 @@ public class ConfigurationService
return obj;
}
}
}
}
catch (Exception e)
{
@@ -71,10 +69,11 @@ public class ConfigurationService
{
ArgumentNullException.ThrowIfNull(conVar);
if (value == null) throw new ArgumentNullException(nameof(value));
if (!conVar.Type.IsInstanceOfType(value))
{
_debugService.Error($"Type mismatch for config {conVar.Name}. Expected {conVar.Type}, got {value.GetType()}.");
_debugService.Error(
$"Type mismatch for config {conVar.Name}. Expected {conVar.Type}, got {value.GetType()}.");
return;
}
@@ -105,7 +104,8 @@ public class ConfigurationService
public static class ConfigExtensions
{
public static bool TryGetConfigValue<T>(this ConfigurationService configurationService, ConVar<T> conVar, [NotNullWhen(true)] out T? value)
public static bool TryGetConfigValue<T>(this ConfigurationService configurationService, ConVar<T> conVar,
[NotNullWhen(true)] out T? value)
{
ArgumentNullException.ThrowIfNull(configurationService);
ArgumentNullException.ThrowIfNull(conVar);

View File

@@ -15,7 +15,8 @@ public partial class ContentService
return fileService.ContentFileApi.Has(item.Hash);
}
public async Task<List<RobustManifestItem>> EnsureItems(ManifestReader manifestReader, Uri downloadUri, ILoadingHandler loadingHandler,
public async Task<List<RobustManifestItem>> EnsureItems(ManifestReader manifestReader, Uri downloadUri,
ILoadingHandler loadingHandler,
CancellationToken cancellationToken)
{
List<RobustManifestItem> allItems = [];
@@ -66,7 +67,8 @@ public partial class ContentService
return await EnsureItems(manifestReader, info.DownloadUri, loadingHandler, cancellationToken);
}
public async Task Unpack(RobustManifestInfo info, IWriteFileApi otherApi, ILoadingHandler loadingHandler, CancellationToken cancellationToken)
public async Task Unpack(RobustManifestInfo info, IWriteFileApi otherApi, ILoadingHandler loadingHandler,
CancellationToken cancellationToken)
{
debugService.Log("Unpack manifest files");
var items = await EnsureItems(info, loadingHandler, cancellationToken);
@@ -83,11 +85,13 @@ public partial class ContentService
{
debugService.Error("OH FUCK!! " + item.Path);
}
loadingHandler.AppendResolvedJob();
}
}
public async Task Download(Uri contentCdn, List<RobustManifestItem> toDownload, ILoadingHandler loadingHandler, CancellationToken cancellationToken)
public async Task Download(Uri contentCdn, List<RobustManifestItem> toDownload, ILoadingHandler loadingHandler,
CancellationToken cancellationToken)
{
if (toDownload.Count == 0 || cancellationToken.IsCancellationRequested)
{
@@ -110,7 +114,8 @@ public partial class ContentService
var request = new HttpRequestMessage(HttpMethod.Post, contentCdn);
request.Headers.Add(
"X-Robust-Download-Protocol",
varService.GetConfigValue(CurrentConVar.ManifestDownloadProtocolVersion).ToString(CultureInfo.InvariantCulture));
varService.GetConfigValue(CurrentConVar.ManifestDownloadProtocolVersion)
.ToString(CultureInfo.InvariantCulture));
request.Content = new ByteArrayContent(requestBody);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
@@ -123,7 +128,7 @@ public partial class ContentService
debugService.Log("Downloading is cancelled!");
return;
}
downloadJobWatch.Dispose();
response.EnsureSuccessStatusCode();
@@ -163,9 +168,9 @@ public partial class ContentService
var readBuffer = new byte[1024];
var i = 0;
loadingHandler.AppendJob(toDownload.Count);
foreach (var item in toDownload)
{
if (cancellationToken.IsCancellationRequested)

View File

@@ -5,20 +5,19 @@ namespace Nebula.Shared.Services;
[ServiceRegister]
public class DebugService : IDisposable
{
public ILogger Logger;
private static string LogPath = Path.Combine(FileService.RootPath, "log");
public DateTime LogDate = DateTime.Now;
public ILogger Logger;
private FileStream LogStream;
private StreamWriter LogWriter;
public DebugService(ILogger logger)
{
Logger = logger;
//if (!Directory.Exists(LogPath))
// Directory.CreateDirectory(LogPath);
// Directory.CreateDirectory(LogPath);
//var filename = String.Format("{0:yyyy-MM-dd}.txt", DateTime.Now);
//LogStream = File.Open(Path.Combine(LogPath, filename),
@@ -26,6 +25,12 @@ public class DebugService : IDisposable
//LogWriter = new StreamWriter(LogStream);
}
public void Dispose()
{
LogWriter.Dispose();
LogStream.Dispose();
}
public void Debug(string message)
{
Log(LoggerCategory.Debug, message);
@@ -44,16 +49,10 @@ public class DebugService : IDisposable
public void Error(Exception e)
{
Error(e.Message + "\r\n" + e.StackTrace);
if(e.InnerException != null)
if (e.InnerException != null)
Error(e.InnerException);
}
public void Dispose()
{
LogWriter.Dispose();
LogStream.Dispose();
}
private void Log(LoggerCategory category, string message)
{
Logger.Log(category, message);

View File

@@ -14,12 +14,12 @@ public sealed class EngineService
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 Task _currInfoTask;
public EngineService(RestService restService, DebugService debugService, ConfigurationService varService,
FileService fileService, IServiceProvider serviceProvider, AssemblyService assemblyService)
{
@@ -52,7 +52,7 @@ public sealed class EngineService
public EngineBuildInfo? GetVersionInfo(string version)
{
CheckAndWaitValidation();
if (!VersionInfos.TryGetValue(version, out var foundVersion))
return null;
@@ -120,7 +120,7 @@ public sealed class EngineService
public EngineBuildInfo? GetModuleBuildInfo(string moduleName, string version)
{
CheckAndWaitValidation();
if (!ModuleInfos.TryGetValue(moduleName, out var module) ||
!module.Versions.TryGetValue(version, out var value))
return null;
@@ -140,7 +140,7 @@ public sealed class EngineService
public string ResolveModuleVersion(string moduleName, string engineVersion)
{
CheckAndWaitValidation();
var engineVersionObj = Version.Parse(engineVersion);
var module = ModuleInfos[moduleName];
var selectedVersion = module.Versions.Select(kv => new { Version = Version.Parse(kv.Key), kv.Key, kv.Value })
@@ -192,9 +192,9 @@ public sealed class EngineService
private void CheckAndWaitValidation()
{
if (_currInfoTask.IsCompleted)
if (_currInfoTask.IsCompleted)
return;
_debugService.Debug("thinks is not done yet, please wait");
_currInfoTask.Wait();
}

View File

@@ -14,12 +14,12 @@ 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 readonly IReadWriteFileApi ConfigurationApi;
private HashApi? _hashApi;
public FileService(DebugService debugService)

View File

@@ -8,43 +8,43 @@ public class HubService
private readonly ConfigurationService _configurationService;
private readonly RestService _restService;
private readonly List<ServerHubInfo> _serverList = new();
public Action<HubServerChangedEventArgs>? HubServerChangedEventArgs;
public Action? HubServerLoaded;
private readonly List<ServerHubInfo> _serverList = new();
public IReadOnlyList<ServerHubInfo> ServerList => _serverList;
private bool _isUpdating = false;
public bool IsUpdating => _isUpdating;
public HubService(ConfigurationService configurationService, RestService restService)
{
_configurationService = configurationService;
_restService = restService;
UpdateHub();
}
public IReadOnlyList<ServerHubInfo> ServerList => _serverList;
public bool IsUpdating { get; private set; }
public async void UpdateHub()
{
if(_isUpdating) return;
if (IsUpdating) return;
_serverList.Clear();
_isUpdating = true;
IsUpdating = true;
HubServerChangedEventArgs?.Invoke(new HubServerChangedEventArgs([], HubServerChangeAction.Clear));
foreach (var urlStr in _configurationService.GetConfigValue(CurrentConVar.Hub)!)
{
var servers = await _restService.GetAsyncDefault<List<ServerHubInfo>>(new Uri(urlStr), [], CancellationToken.None);
var servers =
await _restService.GetAsyncDefault<List<ServerHubInfo>>(new Uri(urlStr), [], CancellationToken.None);
_serverList.AddRange(servers);
HubServerChangedEventArgs?.Invoke(new HubServerChangedEventArgs(servers, HubServerChangeAction.Add));
}
_isUpdating = false;
IsUpdating = false;
HubServerLoaded?.Invoke();
}
}
public class HubServerChangedEventArgs : EventArgs
@@ -61,5 +61,7 @@ public class HubServerChangedEventArgs : EventArgs
public enum HubServerChangeAction
{
Add, Remove, Clear,
Add,
Remove,
Clear
}

View File

@@ -3,12 +3,14 @@ namespace Nebula.Shared.Services;
[ServiceRegister]
public class PopupMessageService
{
public Action<object>? OnPopupRequired;
public Action<object>? OnCloseRequired;
public Action<object>? OnPopupRequired;
public void Popup(object obj)
{
OnPopupRequired?.Invoke(obj);
}
public void ClosePopup(object obj)
{
OnCloseRequired?.Invoke(obj);

View File

@@ -12,7 +12,8 @@ public sealed class RunnerService(
EngineService engineService,
AssemblyService assemblyService)
{
public async Task PrepareRun(RobustBuildInfo buildInfo, ILoadingHandler loadingHandler, CancellationToken cancellationToken)
public async Task PrepareRun(RobustBuildInfo buildInfo, ILoadingHandler loadingHandler,
CancellationToken cancellationToken)
{
debugService.Log("Prepare Content!");
@@ -20,12 +21,13 @@ public sealed class RunnerService(
if (engine is null)
throw new Exception("Engine version is not usable: " + buildInfo.BuildInfo.Build.EngineVersion);
await contentService.EnsureItems(buildInfo.RobustManifestInfo, loadingHandler, cancellationToken);
await engineService.EnsureEngineModules("Robust.Client.WebView", buildInfo.BuildInfo.Build.EngineVersion);
}
public async Task Run(string[] runArgs, RobustBuildInfo buildInfo, IRedialApi redialApi, ILoadingHandler loadingHandler,
public async Task Run(string[] runArgs, RobustBuildInfo buildInfo, IRedialApi redialApi,
ILoadingHandler loadingHandler,
CancellationToken cancellationToken)
{
debugService.Log("Start Content!");
@@ -49,7 +51,8 @@ public sealed class RunnerService(
var args = new MainArgs(runArgs, engine, redialApi, extraMounts);
if (!assemblyService.TryOpenAssembly(varService.GetConfigValue(CurrentConVar.RobustAssemblyName)!, engine, out var clientAssembly))
if (!assemblyService.TryOpenAssembly(varService.GetConfigValue(CurrentConVar.RobustAssemblyName)!, engine,
out var clientAssembly))
throw new Exception("Unable to locate Robust.Client.dll in engine build!");
if (!assemblyService.TryGetLoader(clientAssembly, out var loader))

View File

@@ -7,7 +7,7 @@ namespace Nebula.Shared.Utils;
public static class Helper
{
public static readonly JsonSerializerOptions JsonWebOptions = new(JsonSerializerDefaults.Web);
public static void OpenBrowser(string url)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -22,12 +22,8 @@ public static class Helper
{
Process.Start("open", url);
}
else
{
}
}
public static async Task<T> AsJson<T>(this HttpContent content) where T : notnull
{
var str = await content.ReadAsStringAsync();

View File

@@ -121,7 +121,7 @@ public static class UriHelper
{
return new Uri(GetServerApiAddress(serverAddress), "client.zip");
}
[Pure]
public static Uri AddParameter(this Uri url, string paramName, string paramValue)
{

View File

@@ -0,0 +1,132 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace Nebula.SourceGenerators;
[Generator]
public class DependencyAutoGenerator : IIncrementalGenerator
{
private static readonly string ConstructGeneratorAttributeName = "ConstructGeneratorAttribute";
private static readonly string GeneratePropertyAttributeName = "GeneratePropertyAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
GenerateAttribute(ConstructGeneratorAttributeName, "Class", context);
GenerateAttribute(GeneratePropertyAttributeName, "Property", context);
var provider = context.SyntaxProvider
.CreateSyntaxProvider(
(s, _) => s is ClassDeclarationSyntax,
(ctx, _) => GetClassDeclarationForSourceGen(ctx))
.Where(t => t.reportAttributeFound)
.Select((t, _) => t.Item1);
context.RegisterSourceOutput(context.CompilationProvider.Combine(provider.Collect()),
(ctx, t) => GenerateCode(ctx, t.Left, t.Right));
}
private static void GenerateAttribute(string attributeName, string usage,
IncrementalGeneratorInitializationContext context)
{
var attributeSourceCode = $@"// <auto-generated/>
namespace SourceGen
{{
[System.AttributeUsage(System.AttributeTargets.{usage})]
public class {attributeName} : System.Attribute
{{
}}
}}";
context.RegisterPostInitializationOutput(ctx => ctx.AddSource(
$"{attributeName}.g.cs",
SourceText.From(attributeSourceCode, Encoding.UTF8)));
}
private static (ClassDeclarationSyntax, bool reportAttributeFound) GetClassDeclarationForSourceGen(
GeneratorSyntaxContext context)
{
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists)
foreach (var attributeSyntax in attributeListSyntax.Attributes)
{
if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol)
continue;
var attributeName = attributeSymbol.ContainingType.ToDisplayString();
if (attributeName == $"SourceGen.{ConstructGeneratorAttributeName}")
return (classDeclarationSyntax, true);
}
return (classDeclarationSyntax, false);
}
private void GenerateCode(SourceProductionContext context, Compilation compilation,
ImmutableArray<ClassDeclarationSyntax> classDeclarations)
{
foreach (var classDeclarationSyntax in classDeclarations)
{
var semanticModel = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree);
if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol)
continue;
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
var className = classDeclarationSyntax.Identifier.Text;
var defaultConstruct = $@"public {className}(){{}}";
var propertiesGenerated = GetProperties(classSymbol).ToList();
var constr = propertiesGenerated.Select(a => $"{a.Type.ToDisplayString()} g{a.Name}");
var body = propertiesGenerated.Select(a => $"this.{a.Name} = g{a.Name};");
if (!constr.Any()) defaultConstruct = "";
var code = $@"// <auto-generated/>
using System;
using System.Collections.Generic;
namespace {namespaceName};
partial class {className}
{{
{defaultConstruct}
public {className}(
{string.Join(",\n\t\t", constr)}
) : base(){{
{string.Join("\n\t\t", body)}
if (Avalonia.Controls.Design.IsDesignMode) InitialiseInDesignMode();
else Initialise();
}}
}}
";
// Add the source code to the compilation.
context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8));
}
}
private static IEnumerable<IPropertySymbol> GetProperties(INamedTypeSymbol classSymbol)
{
return classSymbol.GetMembers().OfType<IPropertySymbol>().Where(a =>
HasAttribute(a, $"SourceGen.{GeneratePropertyAttributeName}"));
}
private static bool HasAttribute(ISymbol type, string attributeName)
{
foreach (var attribute in type.GetAttributes())
if (attribute.AttributeClass?.ToDisplayString() == attributeName)
return true;
return false;
}
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>
<RootNamespace>Nebula.SourceGenerators</RootNamespace>
<PackageId>Nebula.SourceGenerators</PackageId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"DebugRoslynSourceGenerator": {
"commandName": "DebugRoslynComponent",
"targetProject": "../Nebula.Shared/Nebula.Shared.csproj"
}
}
}

View File

@@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nebula.Shared", "Nebula.Sha
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nebula.Runner", "Nebula.Runner\Nebula.Runner.csproj", "{82D96367-44B0-4F25-A094-CBE73B052B73}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nebula.SourceGenerators", "Nebula.SourceGenerators\Nebula.SourceGenerators.csproj", "{985A8F36-AFEB-4E85-8EDB-7C9DDEC698DC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +32,9 @@ Global
{82D96367-44B0-4F25-A094-CBE73B052B73}.Debug|Any CPU.Build.0 = Debug|Any CPU
{82D96367-44B0-4F25-A094-CBE73B052B73}.Release|Any CPU.ActiveCfg = Release|Any CPU
{82D96367-44B0-4F25-A094-CBE73B052B73}.Release|Any CPU.Build.0 = Release|Any CPU
{985A8F36-AFEB-4E85-8EDB-7C9DDEC698DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{985A8F36-AFEB-4E85-8EDB-7C9DDEC698DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{985A8F36-AFEB-4E85-8EDB-7C9DDEC698DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{985A8F36-AFEB-4E85-8EDB-7C9DDEC698DC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal