- tweak: rework FileApi for services
- tweak: rework filter think - add: content view button - fix: little fixes in services
This commit is contained in:
72
Nebula.Launcher/Controls/FilterBox.cs
Normal file
72
Nebula.Launcher/Controls/FilterBox.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
|
||||||
|
namespace Nebula.Launcher.Controls;
|
||||||
|
|
||||||
|
public class FilterBox : UserControl
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<ICommand> FilterCommandProperty =
|
||||||
|
AvaloniaProperty.Register<FilterBox, ICommand>(nameof(FilterCommand));
|
||||||
|
|
||||||
|
public ICommand FilterCommand
|
||||||
|
{
|
||||||
|
get => GetValue(FilterCommandProperty);
|
||||||
|
set => SetValue(FilterCommandProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action<FilterBoxChangedEventArgs>? OnFilterChanged {get; set;}
|
||||||
|
|
||||||
|
public string? FilterBoxName {
|
||||||
|
set => filterName.Text = value;
|
||||||
|
get => filterName.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StackPanel filterPanel;
|
||||||
|
private TextBox filterName = new TextBox();
|
||||||
|
|
||||||
|
public FilterBox()
|
||||||
|
{
|
||||||
|
filterPanel = new StackPanel()
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
Spacing = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
Content = filterPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddFilter(string name, string tag)
|
||||||
|
{
|
||||||
|
var checkBox = new CheckBox();
|
||||||
|
checkBox.Content = new TextBlock()
|
||||||
|
{
|
||||||
|
Text = name,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
checkBox.IsCheckedChanged += (_, _) =>
|
||||||
|
{
|
||||||
|
var args = new FilterBoxChangedEventArgs(tag, checkBox.IsChecked ?? false);
|
||||||
|
OnFilterChanged?.Invoke(args);
|
||||||
|
FilterCommand?.Execute(args);
|
||||||
|
};
|
||||||
|
|
||||||
|
filterPanel.Children.Add(checkBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class FilterBoxChangedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public FilterBoxChangedEventArgs(string name, bool @checked)
|
||||||
|
{
|
||||||
|
Tag = name;
|
||||||
|
Checked = @checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Tag {get; private set;}
|
||||||
|
public bool Checked {get; private set;}
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ public partial class MainViewModel : ViewModelBase
|
|||||||
|
|
||||||
[GenerateProperty] private DebugService DebugService { get; } = default!;
|
[GenerateProperty] private DebugService DebugService { get; } = default!;
|
||||||
[GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!;
|
[GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!;
|
||||||
|
[GenerateProperty] private ContentService ContentService { get; } = default!;
|
||||||
[GenerateProperty, DesignConstruct] private ViewHelperService ViewHelperService { get; } = default!;
|
[GenerateProperty, DesignConstruct] private ViewHelperService ViewHelperService { get; } = default!;
|
||||||
[GenerateProperty] private FileService FileService { get; } = default!;
|
[GenerateProperty] private FileService FileService { get; } = default!;
|
||||||
|
|
||||||
@@ -46,9 +47,8 @@ public partial class MainViewModel : ViewModelBase
|
|||||||
|
|
||||||
protected override void InitialiseInDesignMode()
|
protected override void InitialiseInDesignMode()
|
||||||
{
|
{
|
||||||
CurrentPage = ViewHelperService.GetViewModel<AccountInfoViewModel>();
|
|
||||||
Items = new ObservableCollection<ListItemTemplate>(_templates);
|
Items = new ObservableCollection<ListItemTemplate>(_templates);
|
||||||
SelectedListItem = Items.First(vm => vm.ModelType == typeof(AccountInfoViewModel));
|
RequirePage<AccountInfoViewModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Initialise()
|
protected override void Initialise()
|
||||||
@@ -67,7 +67,7 @@ public partial class MainViewModel : ViewModelBase
|
|||||||
loadingHandler.LoadingName = "Migration task, please wait...";
|
loadingHandler.LoadingName = "Migration task, please wait...";
|
||||||
loadingHandler.IsCancellable = false;
|
loadingHandler.IsCancellable = false;
|
||||||
|
|
||||||
if (!FileService.CheckMigration(loadingHandler))
|
if (!ContentService.CheckMigration(loadingHandler))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
OnPopupRequired(loadingHandler);
|
OnPopupRequired(loadingHandler);
|
||||||
@@ -77,10 +77,36 @@ public partial class MainViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
if (value is null) return;
|
if (value is null) return;
|
||||||
|
|
||||||
if (!ViewHelperService.TryGetViewModel(value.ModelType, out var vmb) || vmb is not IViewModelPage viewModelPage) return;
|
if (!ViewHelperService.TryGetViewModel(value.ModelType, out var vmb)) return;
|
||||||
|
|
||||||
viewModelPage.OnPageOpen(value.args);
|
OpenPage(vmb, value.args);
|
||||||
CurrentPage = vmb;
|
}
|
||||||
|
|
||||||
|
public T RequirePage<T>() where T : ViewModelBase, IViewModelPage
|
||||||
|
{
|
||||||
|
if (CurrentPage is T vam) return vam;
|
||||||
|
|
||||||
|
var page = ViewHelperService.GetViewModel<T>();
|
||||||
|
OpenPage(page, null);
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenPage(ViewModelBase obj, object? args)
|
||||||
|
{
|
||||||
|
var tabItems = Items.Where(vm => vm.ModelType == obj.GetType());
|
||||||
|
|
||||||
|
var listItemTemplates = tabItems as ListItemTemplate[] ?? tabItems.ToArray();
|
||||||
|
if (listItemTemplates.Length != 0)
|
||||||
|
{
|
||||||
|
SelectedListItem = listItemTemplates.First();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is IViewModelPage page)
|
||||||
|
{
|
||||||
|
page.OnPageOpen(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentPage = obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PopupMessage(PopupViewModelBase viewModelBase)
|
public void PopupMessage(PopupViewModelBase viewModelBase)
|
||||||
|
|||||||
@@ -146,7 +146,14 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase , IViewModel
|
|||||||
|
|
||||||
private void FillRoot(IEnumerable<ServerHubInfo> infos)
|
private void FillRoot(IEnumerable<ServerHubInfo> infos)
|
||||||
{
|
{
|
||||||
foreach (var info in infos) _root.Add(new ContentEntry(this, info.StatusData.Name, info.Address, info.Address, default!));
|
foreach (var info in infos)
|
||||||
|
_root.Add(new ContentEntry(this, info.StatusData.Name, info.Address, info.Address, default!));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Go(string server, ContentPath path)
|
||||||
|
{
|
||||||
|
ServerText = server;
|
||||||
|
Go(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void Go(ContentPath path)
|
public async void Go(ContentPath path)
|
||||||
@@ -384,6 +391,11 @@ public struct ContentPath
|
|||||||
{
|
{
|
||||||
public List<string> Pathes { get; }
|
public List<string> Pathes { get; }
|
||||||
|
|
||||||
|
public ContentPath()
|
||||||
|
{
|
||||||
|
Pathes = [];
|
||||||
|
}
|
||||||
|
|
||||||
public ContentPath(List<string> pathes)
|
public ContentPath(List<string> pathes)
|
||||||
{
|
{
|
||||||
Pathes = pathes;
|
Pathes = pathes;
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ public partial class ServerListViewModel
|
|||||||
s.IsFavorite = true;
|
s.IsFavorite = true;
|
||||||
FavoriteServers.Add(s);
|
FavoriteServers.Add(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplyFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddFavorite(ServerEntryModelView entryModelView)
|
public void AddFavorite(ServerEntryModelView entryModelView)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Nebula.Launcher.Controls;
|
||||||
using Nebula.Launcher.Services;
|
using Nebula.Launcher.Services;
|
||||||
using Nebula.Launcher.ViewModels.Popup;
|
using Nebula.Launcher.ViewModels.Popup;
|
||||||
using Nebula.Launcher.Views;
|
using Nebula.Launcher.Views;
|
||||||
@@ -27,6 +28,7 @@ public partial class ServerListViewModel : ViewModelBase, IViewModelPage
|
|||||||
|
|
||||||
public ObservableCollection<ServerEntryModelView> Servers { get; }= new();
|
public ObservableCollection<ServerEntryModelView> Servers { get; }= new();
|
||||||
public ObservableCollection<Exception> HubErrors { get; } = new();
|
public ObservableCollection<Exception> HubErrors { get; } = new();
|
||||||
|
public readonly ServerFilter CurrentFilter = new ServerFilter();
|
||||||
|
|
||||||
public Action? OnSearchChange;
|
public Action? OnSearchChange;
|
||||||
[GenerateProperty] private HubService HubService { get; }
|
[GenerateProperty] private HubService HubService { get; }
|
||||||
@@ -39,8 +41,6 @@ public partial class ServerListViewModel : ViewModelBase, IViewModelPage
|
|||||||
|
|
||||||
private List<ServerHubInfo> UnsortedServers { get; } = new();
|
private List<ServerHubInfo> UnsortedServers { get; } = new();
|
||||||
|
|
||||||
private List<string> _filters = new();
|
|
||||||
|
|
||||||
//Design think
|
//Design think
|
||||||
protected override void InitialiseInDesignMode()
|
protected override void InitialiseInDesignMode()
|
||||||
{
|
{
|
||||||
@@ -64,22 +64,21 @@ public partial class ServerListViewModel : ViewModelBase, IViewModelPage
|
|||||||
UpdateFavoriteEntries();
|
UpdateFavoriteEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnFilterChanged(string name, bool active)
|
public void ApplyFilter()
|
||||||
{
|
{
|
||||||
DebugService.Debug($"OnFilterChanged: {name} {active}");
|
foreach (var entry in ServerViewContainer.Items)
|
||||||
if(active)
|
{
|
||||||
_filters.Add(name);
|
entry.ProcessFilter(CurrentFilter);
|
||||||
else
|
}
|
||||||
_filters.Remove(name);
|
}
|
||||||
|
|
||||||
if(IsFavoriteMode)
|
public void OnFilterChanged(FilterBoxChangedEventArgs args)
|
||||||
{
|
{
|
||||||
UpdateFavoriteEntries();
|
if (args.Checked)
|
||||||
}
|
CurrentFilter.Tags.Add(args.Tag);
|
||||||
else
|
else
|
||||||
{
|
CurrentFilter.Tags.Remove(args.Tag);
|
||||||
UpdateServerEntries();
|
ApplyFilter();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HubServerLoadingError(Exception obj)
|
private void HubServerLoadingError(Exception obj)
|
||||||
@@ -96,26 +95,19 @@ public partial class ServerListViewModel : ViewModelBase, IViewModelPage
|
|||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
UnsortedServers.Sort(new ServerComparer());
|
UnsortedServers.Sort(new ServerComparer());
|
||||||
foreach (var info in UnsortedServers.Where(CheckServerThink))
|
foreach (var info in UnsortedServers)
|
||||||
{
|
{
|
||||||
var view = ServerViewContainer.Get(info.Address.ToRobustUrl(), info.StatusData);
|
var view = ServerViewContainer.Get(info.Address.ToRobustUrl(), info.StatusData);
|
||||||
Servers.Add(view);
|
Servers.Add(view);
|
||||||
}
|
}
|
||||||
|
ApplyFilter();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnChangeSearch()
|
private void OnChangeSearch()
|
||||||
{
|
{
|
||||||
if(string.IsNullOrEmpty(SearchText)) return;
|
CurrentFilter.SearchText = SearchText;
|
||||||
|
ApplyFilter();
|
||||||
if(IsFavoriteMode)
|
|
||||||
{
|
|
||||||
UpdateFavoriteEntries();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UpdateServerEntries();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HubServerChangedEventArgs(HubServerChangedEventArgs obj)
|
private void HubServerChangedEventArgs(HubServerChangedEventArgs obj)
|
||||||
@@ -134,18 +126,6 @@ public partial class ServerListViewModel : ViewModelBase, IViewModelPage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckServerThink(ServerHubInfo hubInfo)
|
|
||||||
{
|
|
||||||
var isNameEqual = string.IsNullOrEmpty(SearchText) || hubInfo.StatusData.Name.ToLower().Contains(SearchText.ToLower());
|
|
||||||
|
|
||||||
if (_filters.Count == 0) return isNameEqual;
|
|
||||||
if(_filters.Select(t=>t.Replace('_',':').Replace("ERPYes","18+")).Any(t=>hubInfo.StatusData.Tags.Contains(t) || hubInfo.InferredTags.Contains(t)))
|
|
||||||
return isNameEqual;
|
|
||||||
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FilterRequired()
|
public void FilterRequired()
|
||||||
{
|
{
|
||||||
IsFilterVisible = !IsFilterVisible;
|
IsFilterVisible = !IsFilterVisible;
|
||||||
@@ -179,6 +159,8 @@ public class ServerViewContainer(
|
|||||||
{
|
{
|
||||||
private readonly Dictionary<string, ServerEntryModelView> _entries = new();
|
private readonly Dictionary<string, ServerEntryModelView> _entries = new();
|
||||||
|
|
||||||
|
public ICollection<ServerEntryModelView> Items => _entries.Values;
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_entries.Clear();
|
_entries.Clear();
|
||||||
@@ -241,3 +223,30 @@ public class ServerComparer : IComparer<ServerHubInfo>, IComparer<ServerStatus>,
|
|||||||
return Compare(x.Item2, y.Item2);
|
return Compare(x.Item2, y.Item2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class ServerFilter
|
||||||
|
{
|
||||||
|
public string SearchText { get; set; } = "";
|
||||||
|
public HashSet<string> Tags { get; } = new();
|
||||||
|
public bool IsMatchByName(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(SearchText))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return name.Contains(SearchText, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMatchByTags(IEnumerable<string> itemTags)
|
||||||
|
{
|
||||||
|
if (Tags.Count == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var itemTagSet = new HashSet<string>(itemTags);
|
||||||
|
return Tags.All(tag => itemTagSet.Contains(tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMatch(string name, IEnumerable<string> itemTags)
|
||||||
|
{
|
||||||
|
return IsMatchByName(name) && IsMatchByTags(itemTags);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,16 @@ using System;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using Avalonia.Controls;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Nebula.Launcher.Services;
|
using Nebula.Launcher.Services;
|
||||||
|
using Nebula.Launcher.ViewModels.Pages;
|
||||||
using Nebula.Launcher.ViewModels.Popup;
|
using Nebula.Launcher.ViewModels.Popup;
|
||||||
using Nebula.Launcher.Views;
|
using Nebula.Launcher.Views;
|
||||||
using Nebula.Shared.Models;
|
using Nebula.Shared.Models;
|
||||||
@@ -17,15 +20,25 @@ using Nebula.Shared.Utils;
|
|||||||
|
|
||||||
namespace Nebula.Launcher.ViewModels;
|
namespace Nebula.Launcher.ViewModels;
|
||||||
|
|
||||||
[ViewModelRegister(typeof(ServerEntryView), isSingleton: false)]
|
[ViewModelRegister(typeof(ServerEntryView), false)]
|
||||||
[ConstructGenerator]
|
[ConstructGenerator]
|
||||||
public partial class ServerEntryModelView : ViewModelBase
|
public partial class ServerEntryModelView : ViewModelBase
|
||||||
{
|
{
|
||||||
|
[ObservableProperty] private string _description = "Fetching info...";
|
||||||
|
[ObservableProperty] private bool _expandInfo;
|
||||||
|
[ObservableProperty] private bool _isFavorite;
|
||||||
|
[ObservableProperty] private bool _isVisible;
|
||||||
|
|
||||||
|
private string _lastError = "";
|
||||||
private Process? _p;
|
private Process? _p;
|
||||||
public RobustUrl Address { get; private set; }
|
|
||||||
public Action? OnFavoriteToggle;
|
private ServerInfo? _serverInfo;
|
||||||
|
[ObservableProperty] private bool _tagDataVisible;
|
||||||
|
|
||||||
public LogPopupModelView CurrLog;
|
public LogPopupModelView CurrLog;
|
||||||
|
public Action? OnFavoriteToggle;
|
||||||
|
public RobustUrl Address { get; private set; }
|
||||||
|
|
||||||
[GenerateProperty] private AuthService AuthService { get; } = default!;
|
[GenerateProperty] private AuthService AuthService { get; } = default!;
|
||||||
[GenerateProperty] private ContentService ContentService { get; } = default!;
|
[GenerateProperty] private ContentService ContentService { get; } = default!;
|
||||||
[GenerateProperty] private CancellationService CancellationService { get; } = default!;
|
[GenerateProperty] private CancellationService CancellationService { get; } = default!;
|
||||||
@@ -34,16 +47,12 @@ public partial class ServerEntryModelView : ViewModelBase
|
|||||||
[GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!;
|
[GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!;
|
||||||
[GenerateProperty] private ViewHelperService ViewHelperService { get; } = default!;
|
[GenerateProperty] private ViewHelperService ViewHelperService { get; } = default!;
|
||||||
[GenerateProperty] private RestService RestService { get; } = default!;
|
[GenerateProperty] private RestService RestService { get; } = default!;
|
||||||
|
[GenerateProperty] private MainViewModel MainViewModel { get; } = default!;
|
||||||
[ObservableProperty] private string _description = "Fetching info...";
|
|
||||||
[ObservableProperty] private bool _expandInfo = false;
|
|
||||||
[ObservableProperty] private bool _tagDataVisible = false;
|
|
||||||
[ObservableProperty] private bool _isFavorite = false;
|
|
||||||
|
|
||||||
public ServerStatus Status { get; private set; } =
|
public ServerStatus Status { get; private set; } =
|
||||||
new ServerStatus(
|
new(
|
||||||
"Fetching data...",
|
"Fetching data...",
|
||||||
$"Loading...", [],
|
"Loading...", [],
|
||||||
"",
|
"",
|
||||||
-1,
|
-1,
|
||||||
-1,
|
-1,
|
||||||
@@ -56,28 +65,6 @@ public partial class ServerEntryModelView : ViewModelBase
|
|||||||
public ObservableCollection<ServerLink> Links { get; } = new();
|
public ObservableCollection<ServerLink> Links { get; } = new();
|
||||||
public bool RunVisible => Process == null;
|
public bool RunVisible => Process == null;
|
||||||
|
|
||||||
private ServerInfo? _serverInfo = null;
|
|
||||||
|
|
||||||
private string _lastError = "";
|
|
||||||
|
|
||||||
public async Task<ServerInfo?> GetServerInfo()
|
|
||||||
{
|
|
||||||
if (_serverInfo == null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_serverInfo = await RestService.GetAsync<ServerInfo>(Address.InfoUri, CancellationService.Token);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Description = e.Message;
|
|
||||||
DebugService.Error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _serverInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<string> Tags { get; } = [];
|
public ObservableCollection<string> Tags { get; } = [];
|
||||||
|
|
||||||
public ICommand OnLinkGo { get; } = new LinkGoCommand();
|
public ICommand OnLinkGo { get; } = new LinkGoCommand();
|
||||||
@@ -92,6 +79,22 @@ public partial class ServerEntryModelView : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ServerInfo?> GetServerInfo()
|
||||||
|
{
|
||||||
|
if (_serverInfo == null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_serverInfo = await RestService.GetAsync<ServerInfo>(Address.InfoUri, CancellationService.Token);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Description = e.Message;
|
||||||
|
DebugService.Error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _serverInfo;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void InitialiseInDesignMode()
|
protected override void InitialiseInDesignMode()
|
||||||
{
|
{
|
||||||
Description = "Server of meow girls! Nya~ \nNyaMeow\nOOOINK!!";
|
Description = "Server of meow girls! Nya~ \nNyaMeow\nOOOINK!!";
|
||||||
@@ -109,14 +112,16 @@ public partial class ServerEntryModelView : ViewModelBase
|
|||||||
CurrLog = ViewHelperService.GetViewModel<LogPopupModelView>();
|
CurrLog = ViewHelperService.GetViewModel<LogPopupModelView>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ProcessFilter(ServerFilter serverFilter)
|
||||||
|
{
|
||||||
|
IsVisible = serverFilter.IsMatch(Status.Name, Tags);
|
||||||
|
}
|
||||||
|
|
||||||
public void SetStatus(ServerStatus serverStatus)
|
public void SetStatus(ServerStatus serverStatus)
|
||||||
{
|
{
|
||||||
Status = serverStatus;
|
Status = serverStatus;
|
||||||
Tags.Clear();
|
Tags.Clear();
|
||||||
foreach (var tag in Status.Tags)
|
foreach (var tag in Status.Tags) Tags.Add(tag);
|
||||||
{
|
|
||||||
Tags.Add(tag);
|
|
||||||
}
|
|
||||||
OnPropertyChanged(nameof(Status));
|
OnPropertyChanged(nameof(Status));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,13 +129,9 @@ public partial class ServerEntryModelView : ViewModelBase
|
|||||||
{
|
{
|
||||||
Address = url;
|
Address = url;
|
||||||
if (serverStatus is not null)
|
if (serverStatus is not null)
|
||||||
{
|
|
||||||
SetStatus(serverStatus);
|
SetStatus(serverStatus);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
FetchStatus();
|
FetchStatus();
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -150,6 +151,11 @@ public partial class ServerEntryModelView : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OpenContentViewer()
|
||||||
|
{
|
||||||
|
MainViewModel.RequirePage<ContentBrowserViewModel>().Go(Address.ToString(), new ContentPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void ToggleFavorites()
|
public void ToggleFavorites()
|
||||||
{
|
{
|
||||||
@@ -179,7 +185,7 @@ public partial class ServerEntryModelView : ViewModelBase
|
|||||||
|
|
||||||
await RunnerService.PrepareRun(buildInfo, loadingContext, CancellationService.Token);
|
await RunnerService.PrepareRun(buildInfo, loadingContext, CancellationService.Token);
|
||||||
|
|
||||||
var path = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
|
var path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||||
|
|
||||||
Process = Process.Start(new ProcessStartInfo
|
Process = Process.Start(new ProcessStartInfo
|
||||||
{
|
{
|
||||||
@@ -275,25 +281,16 @@ public partial class ServerEntryModelView : ViewModelBase
|
|||||||
public async void ExpandInfoRequired()
|
public async void ExpandInfoRequired()
|
||||||
{
|
{
|
||||||
ExpandInfo = !ExpandInfo;
|
ExpandInfo = !ExpandInfo;
|
||||||
if (Avalonia.Controls.Design.IsDesignMode)
|
if (Design.IsDesignMode) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = await GetServerInfo();
|
var info = await GetServerInfo();
|
||||||
if (info == null)
|
if (info == null) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Description = info.Desc;
|
Description = info.Desc;
|
||||||
|
|
||||||
Links.Clear();
|
Links.Clear();
|
||||||
if (info.Links is null) return;
|
if (info.Links is null) return;
|
||||||
foreach (var link in info.Links)
|
foreach (var link in info.Links) Links.Add(link);
|
||||||
{
|
|
||||||
Links.Add(link);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FindDotnetPath()
|
private static string FindDotnetPath()
|
||||||
@@ -353,6 +350,7 @@ public class LinkGoCommand : ICommand
|
|||||||
{
|
{
|
||||||
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanExecute(object? parameter)
|
public bool CanExecute(object? parameter)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -120,7 +120,7 @@
|
|||||||
https://cinka.ru/nebula-launcher/
|
https://cinka.ru/nebula-launcher/
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</Button>
|
</Button>
|
||||||
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Center">v0.05-a</TextBlock>
|
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Center">v0.08-a</TextBlock>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Label>
|
</Label>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:pages="clr-namespace:Nebula.Launcher.ViewModels.Pages"
|
xmlns:pages="clr-namespace:Nebula.Launcher.ViewModels.Pages"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="clr-namespace:Nebula.Launcher.Controls">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<pages:ServerListViewModel />
|
<pages:ServerListViewModel />
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
Margin="5,0,0,10"
|
Margin="5,0,0,10"
|
||||||
Padding="0,0,10,0">
|
Padding="0,0,10,0">
|
||||||
<StackPanel>
|
<StackPanel Margin="0,0,0,30">
|
||||||
<ItemsControl ItemsSource="{Binding HubErrors}" Margin="10,0,10,0" />
|
<ItemsControl ItemsSource="{Binding HubErrors}" Margin="10,0,10,0" />
|
||||||
<ItemsControl
|
<ItemsControl
|
||||||
IsVisible="{Binding IsFavoriteMode}"
|
IsVisible="{Binding IsFavoriteMode}"
|
||||||
@@ -41,19 +42,8 @@
|
|||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
IsVisible="{Binding IsFilterVisible}">
|
IsVisible="{Binding IsFilterVisible}">
|
||||||
<StackPanel Orientation="Vertical" Spacing="2" Margin="15">
|
<StackPanel Orientation="Vertical" Spacing="2" Margin="15">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="15">
|
<controls:FilterBox Name="EssentialFilters" FilterBoxName="Roleplay" FilterCommand="{Binding OnFilterChanged}"/>
|
||||||
<TextBlock Text="Roleplay:" VerticalAlignment="Center" Margin="0,0,15,0"/>
|
<controls:FilterBox Name="LanguageFilters" FilterBoxName="Language" FilterCommand="{Binding OnFilterChanged}"/>
|
||||||
<CheckBox Click="Button_OnClick" Name="rp_none"><TextBlock Text="NonRP"/></CheckBox>
|
|
||||||
<CheckBox Click="Button_OnClick" Name="rp_low"><TextBlock Text="LowRP"/></CheckBox>
|
|
||||||
<CheckBox Click="Button_OnClick" Name="rp_med"><TextBlock Text="MediumRP"/></CheckBox>
|
|
||||||
<CheckBox Click="Button_OnClick" Name="rp_high"><TextBlock Text="HardRP"/></CheckBox>
|
|
||||||
<CheckBox Click="Button_OnClick" Name="ERPYes"><TextBlock Text="18+"/></CheckBox>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="15">
|
|
||||||
<TextBlock Text="Language:" VerticalAlignment="Center" Margin="0,0,4,0"/>
|
|
||||||
<CheckBox Click="Button_OnClick" Name="lang_ru"><TextBlock Text="RU"/></CheckBox>
|
|
||||||
<CheckBox Click="Button_OnClick" Name="lang_en"><TextBlock Text="EN"/></CheckBox>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
|
||||||
using ServerListViewModel = Nebula.Launcher.ViewModels.Pages.ServerListViewModel;
|
using ServerListViewModel = Nebula.Launcher.ViewModels.Pages.ServerListViewModel;
|
||||||
|
|
||||||
namespace Nebula.Launcher.Views.Pages;
|
namespace Nebula.Launcher.Views.Pages;
|
||||||
@@ -12,6 +9,15 @@ public partial class ServerListView : UserControl
|
|||||||
public ServerListView()
|
public ServerListView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
EssentialFilters.AddFilter("Non RP", "rp:none");
|
||||||
|
EssentialFilters.AddFilter("Low RP", "rp:low");
|
||||||
|
EssentialFilters.AddFilter("Medium RP", "rp:med");
|
||||||
|
EssentialFilters.AddFilter("Hard RP", "rp:high");
|
||||||
|
EssentialFilters.AddFilter("18+", "18+");
|
||||||
|
|
||||||
|
LanguageFilters.AddFilter("RU","lang:ru");
|
||||||
|
LanguageFilters.AddFilter("EN","lang:en");
|
||||||
}
|
}
|
||||||
|
|
||||||
// This constructor is used when the view is created via dependency injection
|
// This constructor is used when the view is created via dependency injection
|
||||||
@@ -26,11 +32,4 @@ public partial class ServerListView : UserControl
|
|||||||
var context = (ServerListViewModel?)DataContext;
|
var context = (ServerListViewModel?)DataContext;
|
||||||
context?.OnSearchChange?.Invoke();
|
context?.OnSearchChange?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Button_OnClick(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
var send = sender as CheckBox;
|
|
||||||
var context = (ServerListViewModel?)DataContext;
|
|
||||||
context?.OnFilterChanged(send.Name, send.IsChecked.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
x:Class="Nebula.Launcher.Views.ServerEntryView"
|
x:Class="Nebula.Launcher.Views.ServerEntryView"
|
||||||
x:DataType="viewModels:ServerEntryModelView"
|
x:DataType="viewModels:ServerEntryModelView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
|
||||||
xmlns:converters="clr-namespace:Nebula.Launcher.Converters"
|
xmlns:converters="clr-namespace:Nebula.Launcher.Converters"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
@@ -13,7 +12,8 @@
|
|||||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||||
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
|
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
|
||||||
xmlns:views="clr-namespace:Nebula.Launcher.Views"
|
xmlns:views="clr-namespace:Nebula.Launcher.Views"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
IsVisible="{Binding IsVisible}">
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<viewModels:ServerEntryModelView />
|
<viewModels:ServerEntryModelView />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
@@ -234,6 +234,10 @@
|
|||||||
IsVisible="{Binding ExpandInfo}"
|
IsVisible="{Binding ExpandInfo}"
|
||||||
Margin="5,5,0,0"
|
Margin="5,5,0,0"
|
||||||
Spacing="5">
|
Spacing="5">
|
||||||
|
<Button
|
||||||
|
Command="{Binding OpenContentViewer}">
|
||||||
|
<Svg Margin="4" Path="/Assets/svg/folder.svg" />
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
Command="{Binding StopInstance}"
|
Command="{Binding StopInstance}"
|
||||||
CornerRadius="10"
|
CornerRadius="10"
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ namespace Nebula.Shared;
|
|||||||
|
|
||||||
public static class CurrentConVar
|
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");
|
ConVarBuilder.Build<string[]>("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");
|
ConVarBuilder.Build<string[]>("engine.moduleManifestUrl", ["https://robust-builds.cdn.spacestation14.com/modules.json"]);
|
||||||
|
|
||||||
public static readonly ConVar<int> ManifestDownloadProtocolVersion =
|
public static readonly ConVar<int> ManifestDownloadProtocolVersion =
|
||||||
ConVarBuilder.Build("engine.manifestDownloadProtocolVersion", 1);
|
ConVarBuilder.Build("engine.manifestDownloadProtocolVersion", 1);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Nebula.Shared.FileApis.Interfaces;
|
||||||
|
using Robust.LoaderApi;
|
||||||
|
|
||||||
namespace Nebula.Shared.Services;
|
namespace Nebula.Shared.Services;
|
||||||
|
|
||||||
@@ -31,21 +33,24 @@ public static class ConVarBuilder
|
|||||||
public class ConfigurationService
|
public class ConfigurationService
|
||||||
{
|
{
|
||||||
private readonly DebugService _debugService;
|
private readonly DebugService _debugService;
|
||||||
private readonly FileService _fileService;
|
|
||||||
|
public IReadWriteFileApi ConfigurationApi { get; init; }
|
||||||
|
|
||||||
public ConfigurationService(FileService fileService, DebugService debugService)
|
public ConfigurationService(FileService fileService, DebugService debugService)
|
||||||
{
|
{
|
||||||
_fileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
|
|
||||||
_debugService = debugService ?? throw new ArgumentNullException(nameof(debugService));
|
_debugService = debugService ?? throw new ArgumentNullException(nameof(debugService));
|
||||||
|
|
||||||
|
ConfigurationApi = fileService.CreateFileApi("config");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public T? GetConfigValue<T>(ConVar<T> conVar)
|
public T? GetConfigValue<T>(ConVar<T> conVar)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(conVar);
|
ArgumentNullException.ThrowIfNull(conVar);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_fileService.ConfigurationApi.TryOpen(GetFileName(conVar), out var stream))
|
if (ConfigurationApi.TryOpen(GetFileName(conVar), out var stream))
|
||||||
using (stream)
|
using (stream)
|
||||||
{
|
{
|
||||||
var obj = JsonSerializer.Deserialize<T>(stream);
|
var obj = JsonSerializer.Deserialize<T>(stream);
|
||||||
@@ -72,7 +77,7 @@ public class ConfigurationService
|
|||||||
value = default;
|
value = default;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_fileService.ConfigurationApi.TryOpen(GetFileName(conVar), out var stream))
|
if (ConfigurationApi.TryOpen(GetFileName(conVar), out var stream))
|
||||||
using (stream)
|
using (stream)
|
||||||
{
|
{
|
||||||
var obj = JsonSerializer.Deserialize<T>(stream);
|
var obj = JsonSerializer.Deserialize<T>(stream);
|
||||||
@@ -116,7 +121,7 @@ public class ConfigurationService
|
|||||||
writer.Flush();
|
writer.Flush();
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
_fileService.ConfigurationApi.Save(GetFileName(conVar), stream);
|
ConfigurationApi.Save(GetFileName(conVar), stream);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ namespace Nebula.Shared.Services;
|
|||||||
|
|
||||||
public partial class ContentService
|
public partial class ContentService
|
||||||
{
|
{
|
||||||
|
public readonly IReadWriteFileApi ContentFileApi = fileService.CreateFileApi("content");
|
||||||
|
public readonly IReadWriteFileApi ManifestFileApi = fileService.CreateFileApi("manifest");
|
||||||
|
|
||||||
public bool CheckManifestExist(RobustManifestItem item)
|
public bool CheckManifestExist(RobustManifestItem item)
|
||||||
{
|
{
|
||||||
return fileService.ContentFileApi.Has(item.Hash);
|
return ContentFileApi.Has(item.Hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HashApi> EnsureItems(ManifestReader manifestReader, Uri downloadUri,
|
public async Task<HashApi> EnsureItems(ManifestReader manifestReader, Uri downloadUri,
|
||||||
@@ -31,7 +34,7 @@ public partial class ContentService
|
|||||||
allItems.Add(item.Value);
|
allItems.Add(item.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var hashApi = new HashApi(allItems, fileService.ContentFileApi);
|
var hashApi = new HashApi(allItems, ContentFileApi);
|
||||||
|
|
||||||
items = allItems.Where(a=> !hashApi.Has(a)).ToList();
|
items = allItems.Where(a=> !hashApi.Has(a)).ToList();
|
||||||
|
|
||||||
@@ -46,7 +49,7 @@ public partial class ContentService
|
|||||||
{
|
{
|
||||||
debugService.Log("Getting manifest: " + info.Hash);
|
debugService.Log("Getting manifest: " + info.Hash);
|
||||||
|
|
||||||
if (fileService.ManifestFileApi.TryOpen(info.Hash, out var stream))
|
if (ManifestFileApi.TryOpen(info.Hash, out var stream))
|
||||||
{
|
{
|
||||||
debugService.Log("Loading manifest from: " + info.Hash);
|
debugService.Log("Loading manifest from: " + info.Hash);
|
||||||
return await EnsureItems(new ManifestReader(stream), info.DownloadUri, loadingHandler, cancellationToken);
|
return await EnsureItems(new ManifestReader(stream), info.DownloadUri, loadingHandler, cancellationToken);
|
||||||
@@ -58,7 +61,7 @@ public partial class ContentService
|
|||||||
if (!response.IsSuccessStatusCode) throw new Exception();
|
if (!response.IsSuccessStatusCode) throw new Exception();
|
||||||
|
|
||||||
await using var streamContent = await response.Content.ReadAsStreamAsync(cancellationToken);
|
await using var streamContent = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||||
fileService.ManifestFileApi.Save(info.Hash, streamContent);
|
ManifestFileApi.Save(info.Hash, streamContent);
|
||||||
streamContent.Seek(0, SeekOrigin.Begin);
|
streamContent.Seek(0, SeekOrigin.Begin);
|
||||||
using var manifestReader = new ManifestReader(streamContent);
|
using var manifestReader = new ManifestReader(streamContent);
|
||||||
return await EnsureItems(manifestReader, info.DownloadUri, loadingHandler, cancellationToken);
|
return await EnsureItems(manifestReader, info.DownloadUri, loadingHandler, cancellationToken);
|
||||||
@@ -91,8 +94,6 @@ public partial class ContentService
|
|||||||
loadingHandler.AppendResolvedJob();
|
loadingHandler.AppendResolvedJob();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (loadingHandler is IDisposable disposable)
|
if (loadingHandler is IDisposable disposable)
|
||||||
{
|
{
|
||||||
disposable.Dispose();
|
disposable.Dispose();
|
||||||
|
|||||||
45
Nebula.Shared/Services/ContentService.Migration.cs
Normal file
45
Nebula.Shared/Services/ContentService.Migration.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using Nebula.Shared.FileApis;
|
||||||
|
using Nebula.Shared.Models;
|
||||||
|
|
||||||
|
namespace Nebula.Shared.Services;
|
||||||
|
|
||||||
|
public partial class ContentService
|
||||||
|
{
|
||||||
|
public bool CheckMigration(ILoadingHandler loadingHandler)
|
||||||
|
{
|
||||||
|
debugService.Log("Checking migration...");
|
||||||
|
|
||||||
|
var migrationList = ContentFileApi.AllFiles.Where(f => !f.Contains("\\")).ToList();
|
||||||
|
if(migrationList.Count == 0) return false;
|
||||||
|
|
||||||
|
debugService.Log($"Found {migrationList.Count} migration files. Starting migration...");
|
||||||
|
Task.Run(() => DoMigration(loadingHandler, migrationList));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoMigration(ILoadingHandler loadingHandler, List<string> migrationList)
|
||||||
|
{
|
||||||
|
loadingHandler.SetJobsCount(migrationList.Count);
|
||||||
|
|
||||||
|
Parallel.ForEach(migrationList, (f,_)=>MigrateFile(f,loadingHandler));
|
||||||
|
|
||||||
|
if (loadingHandler is IDisposable disposable)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MigrateFile(string file, ILoadingHandler loadingHandler)
|
||||||
|
{
|
||||||
|
if(!ContentFileApi.TryOpen(file, out var stream))
|
||||||
|
{
|
||||||
|
loadingHandler.AppendResolvedJob();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentFileApi.Save(HashApi.GetManifestPath(file), stream);
|
||||||
|
stream.Dispose();
|
||||||
|
ContentFileApi.Remove(file);
|
||||||
|
loadingHandler.AppendResolvedJob();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Nebula.Shared.FileApis;
|
using Nebula.Shared.FileApis;
|
||||||
|
using Nebula.Shared.FileApis.Interfaces;
|
||||||
using Nebula.Shared.Models;
|
using Nebula.Shared.Models;
|
||||||
using Nebula.Shared.Utils;
|
using Nebula.Shared.Utils;
|
||||||
|
|
||||||
@@ -12,80 +13,68 @@ public sealed class EngineService
|
|||||||
private readonly DebugService _debugService;
|
private readonly DebugService _debugService;
|
||||||
private readonly FileService _fileService;
|
private readonly FileService _fileService;
|
||||||
private readonly RestService _restService;
|
private readonly RestService _restService;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
private readonly ConfigurationService _varService;
|
private readonly ConfigurationService _varService;
|
||||||
|
|
||||||
private readonly Task _currInfoTask;
|
private readonly Task _currInfoTask;
|
||||||
|
private readonly IReadWriteFileApi _engineFileApi;
|
||||||
|
|
||||||
public Dictionary<string, Module> ModuleInfos = default!;
|
private ModulesInfo _modulesInfo = default!;
|
||||||
public Dictionary<string, EngineVersionInfo> VersionInfos = default!;
|
private Dictionary<string, EngineVersionInfo> _versionsInfo = default!;
|
||||||
|
|
||||||
public EngineService(RestService restService, DebugService debugService, ConfigurationService varService,
|
public EngineService(RestService restService, DebugService debugService, ConfigurationService varService,
|
||||||
FileService fileService, IServiceProvider serviceProvider, AssemblyService assemblyService)
|
FileService fileService, AssemblyService assemblyService)
|
||||||
{
|
{
|
||||||
_restService = restService;
|
_restService = restService;
|
||||||
_debugService = debugService;
|
_debugService = debugService;
|
||||||
_varService = varService;
|
_varService = varService;
|
||||||
_fileService = fileService;
|
_fileService = fileService;
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
_assemblyService = assemblyService;
|
_assemblyService = assemblyService;
|
||||||
|
|
||||||
|
_engineFileApi = fileService.CreateFileApi("engine");
|
||||||
|
|
||||||
_currInfoTask = Task.Run(() => LoadEngineManifest(CancellationToken.None));
|
_currInfoTask = Task.Run(() => LoadEngineManifest(CancellationToken.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LoadEngineManifest(CancellationToken cancellationToken)
|
public async Task LoadEngineManifest(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_versionsInfo = await LoadExacManifest(CurrentConVar.EngineManifestUrl, CurrentConVar.EngineManifestBackup, cancellationToken);
|
||||||
|
_modulesInfo = await LoadExacManifest(CurrentConVar.EngineModuleManifestUrl, CurrentConVar.ModuleManifestBackup, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<T> LoadExacManifest<T>(ConVar<string[]> conVar,ConVar<T> backup,CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var manifestUrls = _varService.GetConfigValue(conVar)!;
|
||||||
|
|
||||||
|
foreach (var manifestUrl in manifestUrls)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_debugService.Log("Fetching engine manifest from: " + CurrentConVar.EngineManifestUrl);
|
_debugService.Log("Fetching engine manifest from: " + manifestUrl);
|
||||||
var info = await _restService.GetAsync<Dictionary<string, EngineVersionInfo>>(
|
var info = await _restService.GetAsync<T>(
|
||||||
new Uri(_varService.GetConfigValue(CurrentConVar.EngineManifestUrl)!), cancellationToken);
|
new Uri(manifestUrl), cancellationToken);
|
||||||
|
|
||||||
VersionInfos = info;
|
_varService.SetConfigValue(backup, info);
|
||||||
|
return info;
|
||||||
_varService.SetConfigValue(CurrentConVar.EngineManifestBackup, info);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_debugService.Debug("Trying fallback engine manifest...");
|
_debugService.Error($"error while attempt fetch engine manifest: {e.Message}");
|
||||||
if (!_varService.TryGetConfigValue(CurrentConVar.EngineManifestBackup, out var engineInfo))
|
}
|
||||||
{
|
|
||||||
throw new Exception("No engine info data available",e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VersionInfos = engineInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_debugService.Log("Fetching module manifest from: " + CurrentConVar.EngineModuleManifestUrl);
|
|
||||||
var moduleInfo = await _restService.GetAsync<ModulesInfo>(
|
|
||||||
new Uri(_varService.GetConfigValue(CurrentConVar.EngineModuleManifestUrl)!), cancellationToken);
|
|
||||||
|
|
||||||
if (moduleInfo is null)
|
|
||||||
throw new Exception("Module version info is null");
|
|
||||||
|
|
||||||
ModuleInfos = moduleInfo.Modules;
|
|
||||||
_varService.SetConfigValue(CurrentConVar.ModuleManifestBackup, moduleInfo);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_debugService.Debug("Trying fallback module manifest...");
|
_debugService.Debug("Trying fallback module manifest...");
|
||||||
if (!_varService.TryGetConfigValue(CurrentConVar.ModuleManifestBackup, out var modulesInfo))
|
if (!_varService.TryGetConfigValue(backup, out var moduleInfo))
|
||||||
{
|
{
|
||||||
throw new Exception("No module info data available",e);
|
throw new Exception("No module info data available");
|
||||||
}
|
|
||||||
|
|
||||||
ModuleInfos = modulesInfo.Modules;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return moduleInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EngineBuildInfo? GetVersionInfo(string version)
|
public EngineBuildInfo? GetVersionInfo(string version)
|
||||||
{
|
{
|
||||||
CheckAndWaitValidation();
|
CheckAndWaitValidation();
|
||||||
|
|
||||||
if (!VersionInfos.TryGetValue(version, out var foundVersion))
|
if (!_versionsInfo.TryGetValue(version, out var foundVersion))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (foundVersion.RedirectVersion != null)
|
if (foundVersion.RedirectVersion != null)
|
||||||
@@ -113,12 +102,12 @@ public sealed class EngineService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var api = _fileService.OpenZip(version, _fileService.EngineFileApi);
|
var api = _fileService.OpenZip(version, _engineFileApi);
|
||||||
if (api != null) return _assemblyService.Mount(api);
|
if (api != null) return _assemblyService.Mount(api);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
_fileService.EngineFileApi.Remove(version);
|
_engineFileApi.Remove(version);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,13 +122,13 @@ public sealed class EngineService
|
|||||||
_debugService.Log("Downloading engine version " + version);
|
_debugService.Log("Downloading engine version " + version);
|
||||||
using var client = new HttpClient();
|
using var client = new HttpClient();
|
||||||
var s = await client.GetStreamAsync(info.Url);
|
var s = await client.GetStreamAsync(info.Url);
|
||||||
_fileService.EngineFileApi.Save(version, s);
|
_engineFileApi.Save(version, s);
|
||||||
await s.DisposeAsync();
|
await s.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryOpen(string version, [NotNullWhen(true)] out Stream? stream)
|
public bool TryOpen(string version, [NotNullWhen(true)] out Stream? stream)
|
||||||
{
|
{
|
||||||
return _fileService.EngineFileApi.TryOpen(version, out stream);
|
return _engineFileApi.TryOpen(version, out stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryOpen(string version)
|
public bool TryOpen(string version)
|
||||||
@@ -153,7 +142,7 @@ public sealed class EngineService
|
|||||||
{
|
{
|
||||||
CheckAndWaitValidation();
|
CheckAndWaitValidation();
|
||||||
|
|
||||||
if (!ModuleInfos.TryGetValue(moduleName, out var module) ||
|
if (!_modulesInfo.Modules.TryGetValue(moduleName, out var module) ||
|
||||||
!module.Versions.TryGetValue(version, out var value))
|
!module.Versions.TryGetValue(version, out var value))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -174,7 +163,7 @@ public sealed class EngineService
|
|||||||
CheckAndWaitValidation();
|
CheckAndWaitValidation();
|
||||||
|
|
||||||
var engineVersionObj = Version.Parse(engineVersion);
|
var engineVersionObj = Version.Parse(engineVersion);
|
||||||
var module = ModuleInfos[moduleName];
|
var module = _modulesInfo.Modules[moduleName];
|
||||||
var selectedVersion = module.Versions.Select(kv => new { Version = Version.Parse(kv.Key), kv.Key, kv })
|
var selectedVersion = module.Versions.Select(kv => new { Version = Version.Parse(kv.Key), kv.Key, kv })
|
||||||
.Where(kv => engineVersionObj >= kv.Version)
|
.Where(kv => engineVersionObj >= kv.Version)
|
||||||
.MaxBy(kv => kv.Version);
|
.MaxBy(kv => kv.Version);
|
||||||
@@ -196,11 +185,11 @@ public sealed class EngineService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _assemblyService.Mount(_fileService.OpenZip(fileName, _fileService.EngineFileApi) ?? throw new InvalidOperationException($"{fileName} is not exist!"));
|
return _assemblyService.Mount(_fileService.OpenZip(fileName, _engineFileApi) ?? throw new InvalidOperationException($"{fileName} is not exist!"));
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
_fileService.EngineFileApi.Remove(fileName);
|
_engineFileApi.Remove(fileName);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,7 +202,7 @@ public sealed class EngineService
|
|||||||
_debugService.Log("Downloading engine module version " + moduleVersion);
|
_debugService.Log("Downloading engine module version " + moduleVersion);
|
||||||
using var client = new HttpClient();
|
using var client = new HttpClient();
|
||||||
var s = await client.GetStreamAsync(info.Url);
|
var s = await client.GetStreamAsync(info.Url);
|
||||||
_fileService.EngineFileApi.Save(ConcatName(moduleName, moduleVersion), s);
|
_engineFileApi.Save(ConcatName(moduleName, moduleVersion), s);
|
||||||
await s.DisposeAsync();
|
await s.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,59 +15,12 @@ public class FileService
|
|||||||
|
|
||||||
private readonly DebugService _debugService;
|
private readonly DebugService _debugService;
|
||||||
|
|
||||||
public readonly IReadWriteFileApi ConfigurationApi;
|
|
||||||
public readonly IReadWriteFileApi ContentFileApi;
|
|
||||||
public readonly IReadWriteFileApi EngineFileApi;
|
|
||||||
public readonly IReadWriteFileApi ManifestFileApi;
|
|
||||||
public FileService(DebugService debugService)
|
public FileService(DebugService debugService)
|
||||||
{
|
{
|
||||||
_debugService = debugService;
|
_debugService = debugService;
|
||||||
|
|
||||||
if(!Directory.Exists(RootPath))
|
if(!Directory.Exists(RootPath))
|
||||||
Directory.CreateDirectory(RootPath);
|
Directory.CreateDirectory(RootPath);
|
||||||
|
|
||||||
ContentFileApi = CreateFileApi("content");
|
|
||||||
EngineFileApi = CreateFileApi("engine");
|
|
||||||
ManifestFileApi = CreateFileApi("manifest");
|
|
||||||
ConfigurationApi = CreateFileApi("config");
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CheckMigration(ILoadingHandler loadingHandler)
|
|
||||||
{
|
|
||||||
_debugService.Log("Checking migration...");
|
|
||||||
|
|
||||||
var migrationList = ContentFileApi.AllFiles.Where(f => !f.Contains("\\")).ToList();
|
|
||||||
if(migrationList.Count == 0) return false;
|
|
||||||
|
|
||||||
_debugService.Log($"Found {migrationList.Count} migration files. Starting migration...");
|
|
||||||
Task.Run(() => DoMigration(loadingHandler, migrationList));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DoMigration(ILoadingHandler loadingHandler, List<string> migrationList)
|
|
||||||
{
|
|
||||||
loadingHandler.SetJobsCount(migrationList.Count);
|
|
||||||
|
|
||||||
Parallel.ForEach(migrationList, (f,_)=>MigrateFile(f,loadingHandler));
|
|
||||||
|
|
||||||
if (loadingHandler is IDisposable disposable)
|
|
||||||
{
|
|
||||||
disposable.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MigrateFile(string file, ILoadingHandler loadingHandler)
|
|
||||||
{
|
|
||||||
if(!ContentFileApi.TryOpen(file, out var stream))
|
|
||||||
{
|
|
||||||
loadingHandler.AppendResolvedJob();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentFileApi.Save(HashApi.GetManifestPath(file), stream);
|
|
||||||
stream.Dispose();
|
|
||||||
ContentFileApi.Remove(file);
|
|
||||||
loadingHandler.AppendResolvedJob();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadWriteFileApi CreateFileApi(string path)
|
public IReadWriteFileApi CreateFileApi(string path)
|
||||||
|
|||||||
@@ -43,10 +43,20 @@ public sealed class RunnerService(
|
|||||||
new(hashApi, "/")
|
new(hashApi, "/")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (hashApi.TryOpen("manifest.yml", out var stream))
|
||||||
|
{
|
||||||
|
var modules = ContentManifestParser.ExtractModules(stream);
|
||||||
|
|
||||||
|
foreach (var moduleStr in modules)
|
||||||
|
{
|
||||||
var module =
|
var module =
|
||||||
await engineService.EnsureEngineModules("Robust.Client.WebView", buildInfo.BuildInfo.Build.EngineVersion);
|
await engineService.EnsureEngineModules(moduleStr, buildInfo.BuildInfo.Build.EngineVersion);
|
||||||
if (module is not null)
|
if (module is not null)
|
||||||
extraMounts.Add(new ApiMount(module, "/"));
|
extraMounts.Add(new ApiMount(module, "/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
await stream.DisposeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
var args = new MainArgs(runArgs, engine, redialApi, extraMounts);
|
var args = new MainArgs(runArgs, engine, redialApi, extraMounts);
|
||||||
|
|
||||||
@@ -60,3 +70,45 @@ public sealed class RunnerService(
|
|||||||
await Task.Run(() => loader.Main(args), cancellationToken);
|
await Task.Run(() => loader.Main(args), cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ContentManifestParser
|
||||||
|
{
|
||||||
|
public static List<string> ExtractModules(Stream manifestStream)
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(manifestStream);
|
||||||
|
return ExtractModules(reader.ReadToEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> ExtractModules(string manifestContent)
|
||||||
|
{
|
||||||
|
var modules = new List<string>();
|
||||||
|
var lines = manifestContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
bool inModulesSection = false;
|
||||||
|
|
||||||
|
foreach (var rawLine in lines)
|
||||||
|
{
|
||||||
|
var line = rawLine.Trim();
|
||||||
|
|
||||||
|
if (line.StartsWith("modules:"))
|
||||||
|
{
|
||||||
|
inModulesSection = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inModulesSection)
|
||||||
|
{
|
||||||
|
if (line.StartsWith("- "))
|
||||||
|
{
|
||||||
|
modules.Add(line.Substring(2).Trim());
|
||||||
|
}
|
||||||
|
else if (!line.StartsWith(" "))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AButton_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fcc84c38d8785b88e166e6741b6a4c0dfa09eaf6e41eb151b255817e11f27570_003FButton_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACancellationToken_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F2565b9d99fdde488bc7801b84387b2cc864959cfb63212e1ff576fc9c6bb7e_003FCancellationToken_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACancellationToken_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F2565b9d99fdde488bc7801b84387b2cc864959cfb63212e1ff576fc9c6bb7e_003FCancellationToken_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConsole_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ffd57398b7dc3a8ce7da2786f2c67289c3d974658a9e90d0c1e84db3d965fbf1_003FConsole_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConsole_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ffd57398b7dc3a8ce7da2786f2c67289c3d974658a9e90d0c1e84db3d965fbf1_003FConsole_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFrozenDictionary_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F89dff9063ddb01ff8125b579122b88bf4de94526490d77bcbbef7d0ee662a_003FFrozenDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFrozenDictionary_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F89dff9063ddb01ff8125b579122b88bf4de94526490d77bcbbef7d0ee662a_003FFrozenDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
|||||||
Reference in New Issue
Block a user