Files
NebulaLauncher/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs

427 lines
12 KiB
C#
Raw Normal View History

2025-01-10 12:28:29 +03:00
using System;
2025-01-11 20:39:58 +03:00
using System.Collections.Frozen;
using System.Collections.Generic;
2025-01-10 12:28:29 +03:00
using System.Collections.ObjectModel;
2025-01-12 15:15:01 +03:00
using System.Diagnostics;
2025-01-11 20:39:58 +03:00
using System.Diagnostics.CodeAnalysis;
2025-01-12 15:15:01 +03:00
using System.IO;
2025-01-11 20:39:58 +03:00
using System.Linq;
using System.Threading.Tasks;
2025-01-10 12:28:29 +03:00
using CommunityToolkit.Mvvm.ComponentModel;
2025-01-14 22:10:16 +03:00
using Nebula.Launcher.Services;
2025-03-12 14:51:47 +03:00
using Nebula.Launcher.ViewModels.ContentView;
2025-01-14 22:10:16 +03:00
using Nebula.Launcher.ViewModels.Popup;
2025-01-10 12:28:29 +03:00
using Nebula.Launcher.Views.Pages;
using Nebula.Shared.FileApis;
2025-01-10 12:28:29 +03:00
using Nebula.Shared.Models;
2025-01-11 20:39:58 +03:00
using Nebula.Shared.Services;
using Nebula.Shared.Utils;
2025-01-10 12:28:29 +03:00
2025-01-14 22:10:16 +03:00
namespace Nebula.Launcher.ViewModels.Pages;
2025-01-10 12:28:29 +03:00
[ViewModelRegister(typeof(ContentBrowserView))]
2025-01-14 22:10:16 +03:00
[ConstructGenerator]
2025-01-29 12:32:42 +03:00
public sealed partial class ContentBrowserViewModel : ViewModelBase , IViewModelPage
2025-01-10 12:28:29 +03:00
{
2025-01-11 20:39:58 +03:00
private readonly List<ContentEntry> _root = new();
2025-01-14 22:10:16 +03:00
private readonly List<string> _history = new();
2025-01-10 12:28:29 +03:00
[ObservableProperty] private string _message = "";
[ObservableProperty] private string _searchText = "";
2025-01-11 20:39:58 +03:00
private ContentEntry? _selectedEntry;
2025-01-14 22:10:16 +03:00
[ObservableProperty] private string _serverText = "";
2025-03-12 14:51:47 +03:00
[ObservableProperty] private ContentViewBase? _contentView;
public bool IsCustomContenView => ContentView != null;
2025-01-14 22:10:16 +03:00
[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 IServiceProvider ServiceProvider {get;}
2025-01-22 21:06:05 +03:00
[GenerateProperty, DesignConstruct] private ViewHelperService ViewHelperService { get; } = default!;
2025-01-14 22:10:16 +03:00
public ObservableCollection<ContentEntry> Entries { get; } = new();
2025-01-11 20:39:58 +03:00
2025-03-12 14:51:47 +03:00
private Dictionary<string, Type> _contentContainers = new();
2025-01-11 20:39:58 +03:00
public ContentEntry? SelectedEntry
{
get => _selectedEntry;
set
{
var oldSearchText = SearchText;
2025-03-12 14:51:47 +03:00
SearchText = value?.GetPath().ToString() ?? "";
2025-03-12 14:51:47 +03:00
ContentView = null;
Entries.Clear();
_selectedEntry = value;
2025-03-12 14:51:47 +03:00
if (value == null) return;
2025-01-14 22:10:16 +03:00
if(value.TryOpen(out var stream, out var item)){
2025-01-14 22:10:16 +03:00
var ext = Path.GetExtension(item.Value.Path);
var myTempFile = Path.Combine(Path.GetTempPath(), "tempie" + ext);
2025-01-14 22:10:16 +03:00
if(TryGetContentViewer(ext, out var contentViewBase)){
DebugService.Debug($"Opening custom context:{item.Value.Path}");
2025-03-14 18:32:20 +03:00
contentViewBase.InitialiseWithData(value.GetPath(), stream, value);
stream.Dispose();
ContentView = contentViewBase;
return;
}
2025-01-14 22:10:16 +03:00
var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None);
stream.CopyTo(sw);
2025-01-12 15:15:01 +03:00
sw.Dispose();
stream.Dispose();
2025-01-14 22:10:16 +03:00
var startInfo = new ProcessStartInfo(myTempFile)
{
UseShellExecute = true
};
2025-01-14 22:10:16 +03:00
DebugService.Log("Opening " + myTempFile);
Process.Start(startInfo);
2025-01-12 15:15:01 +03:00
return;
}
if(SearchText.Length > oldSearchText.Length)
AppendHistory(oldSearchText);
2025-01-14 22:10:16 +03:00
foreach (var (_, entryCh) in value.Childs) Entries.Add(entryCh);
2025-01-11 20:39:58 +03:00
}
}
2025-03-12 14:51:47 +03:00
private bool TryGetContentViewer(string type,[NotNullWhen(true)] out ContentViewBase? contentViewBase){
contentViewBase = null;
if(!_contentContainers.TryGetValue(type, out var contentViewType) ||
!contentViewType.IsAssignableTo(typeof(ContentViewBase)))
return false;
contentViewBase = (ContentViewBase)ServiceProvider.GetService(contentViewType)!;
2025-03-12 14:51:47 +03:00
return true;
}
2025-01-10 12:28:29 +03:00
2025-01-14 22:10:16 +03:00
protected override void InitialiseInDesignMode()
2025-01-10 12:28:29 +03:00
{
var a = new ContentEntry(this, "A:", "A", "", default!);
var b = new ContentEntry(this, "B", "B", "", default!);
2025-01-11 20:39:58 +03:00
a.TryAddChild(b);
2025-01-14 22:10:16 +03:00
Entries.Add(a);
2025-01-11 20:39:58 +03:00
}
2025-01-14 22:10:16 +03:00
protected override void Initialise()
2025-01-11 20:39:58 +03:00
{
2025-01-14 22:10:16 +03:00
FillRoot(HubService.ServerList);
HubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
HubService.HubServerLoaded += GoHome;
if (!HubService.IsUpdating) GoHome();
_contentContainers.Add(".dll",typeof(DecompilerContentView));
2025-01-10 12:28:29 +03:00
}
2025-01-11 20:39:58 +03:00
private void GoHome()
2025-01-10 12:28:29 +03:00
{
2025-01-11 20:39:58 +03:00
SelectedEntry = null;
2025-01-14 22:10:16 +03:00
foreach (var entry in _root) Entries.Add(entry);
2025-01-10 12:28:29 +03:00
}
2025-01-11 20:39:58 +03:00
private void HubServerChangedEventArgs(HubServerChangedEventArgs obj)
{
2025-01-14 22:10:16 +03:00
if (obj.Action == HubServerChangeAction.Clear) _root.Clear();
if (obj.Action == HubServerChangeAction.Add) FillRoot(obj.Items);
2025-01-11 20:39:58 +03:00
}
2025-01-10 12:28:29 +03:00
2025-01-12 22:49:32 +03:00
private void FillRoot(IEnumerable<ServerHubInfo> infos)
{
foreach (var info in infos) _root.Add(new ContentEntry(this, info.StatusData.Name, info.Address, info.Address, default!));
2025-01-12 22:49:32 +03:00
}
public async void Go(ContentPath path)
2025-01-10 12:28:29 +03:00
{
2025-01-12 22:49:32 +03:00
if (path.Pathes.Count > 0 && (path.Pathes[0].StartsWith("ss14://") || path.Pathes[0].StartsWith("ss14s://")))
{
ServerText = path.Pathes[0];
2025-01-13 19:24:12 +03:00
path = new ContentPath("");
2025-01-12 22:49:32 +03:00
}
2025-01-14 22:10:16 +03:00
2025-01-12 22:49:32 +03:00
if (string.IsNullOrEmpty(ServerText))
2025-01-11 20:39:58 +03:00
{
SearchText = "";
GoHome();
return;
}
2025-01-14 22:10:16 +03:00
if (ServerText != SelectedEntry?.ServerName) SelectedEntry = await CreateEntry(ServerText);
DebugService.Debug("Going to:" + path.Path);
2025-01-12 22:49:32 +03:00
var oriPath = path.Clone();
2025-01-11 20:39:58 +03:00
try
{
2025-01-12 22:49:32 +03:00
if (SelectedEntry == null || !SelectedEntry.GetRoot().TryGetEntry(path, out var centry))
2025-01-13 19:24:12 +03:00
throw new Exception("Not found! " + oriPath.Path);
2025-01-14 22:10:16 +03:00
2025-01-12 15:15:01 +03:00
SelectedEntry = centry;
2025-01-11 20:39:58 +03:00
}
catch (Exception e)
{
2025-01-14 22:10:16 +03:00
PopupService.Popup(e);
2025-01-11 20:39:58 +03:00
}
}
public void OnBackEnter()
{
Go(new ContentPath(GetHistory()));
2025-01-10 12:28:29 +03:00
}
2025-01-14 22:10:16 +03:00
2025-01-10 12:28:29 +03:00
public void OnGoEnter()
{
2025-01-11 20:39:58 +03:00
Go(new ContentPath(SearchText));
}
private async Task<ContentEntry> CreateEntry(string serverUrl)
{
2025-01-14 22:10:16 +03:00
var loading = ViewHelperService.GetViewModel<LoadingContextViewModel>();
2025-01-11 20:39:58 +03:00
loading.LoadingName = "Loading entry";
2025-01-14 22:10:16 +03:00
PopupService.Popup(loading);
2025-03-12 14:51:47 +03:00
var rurl = serverUrl.ToRobustUrl();
var info = await ContentService.GetBuildInfo(rurl, CancellationService.Token);
2025-03-13 14:58:47 +03:00
var hashApi = await ContentService.EnsureItems(info.RobustManifestInfo, loading,
2025-01-14 22:10:16 +03:00
CancellationService.Token);
2025-01-11 20:39:58 +03:00
var rootEntry = new ContentEntry(this, "", "", serverUrl, default!);
2025-01-11 20:39:58 +03:00
2025-03-13 14:58:47 +03:00
foreach (var item in hashApi.Manifest.Values)
2025-01-11 20:39:58 +03:00
{
var path = new ContentPath(item.Path);
rootEntry.CreateItem(path, item, hashApi);
2025-01-11 20:39:58 +03:00
}
2025-01-14 22:10:16 +03:00
2025-01-11 20:39:58 +03:00
loading.Dispose();
return rootEntry;
}
private void AppendHistory(string str)
{
2025-01-14 22:10:16 +03:00
if (_history.Count >= 10) _history.RemoveAt(9);
2025-01-11 20:39:58 +03:00
_history.Insert(0, str);
}
private string GetHistory()
{
if (_history.Count == 0) return "";
var h = _history[0];
_history.RemoveAt(0);
return h;
}
2025-01-29 12:32:42 +03:00
public void OnPageOpen(object? args)
{
}
2025-01-10 12:28:29 +03:00
}
2025-01-11 20:39:58 +03:00
public class ContentEntry
2025-01-10 12:28:29 +03:00
{
2025-01-14 22:10:16 +03:00
private readonly Dictionary<string, ContentEntry> _childs = new();
private readonly ContentBrowserViewModel _viewModel;
private HashApi _fileApi;
2025-01-14 22:10:16 +03:00
public RobustManifestItem? Item { get; private set; }
2025-01-14 22:10:16 +03:00
internal ContentEntry(ContentBrowserViewModel viewModel, string name, string pathName, string serverName, HashApi fileApi)
2025-01-14 22:10:16 +03:00
{
Name = name;
ServerName = serverName;
PathName = pathName;
_viewModel = viewModel;
_fileApi = fileApi;
2025-01-14 22:10:16 +03:00
}
2025-01-11 20:39:58 +03:00
public bool IsDirectory => Item == null;
public string Name { get; private set; }
2025-01-14 22:10:16 +03:00
public string PathName { get; }
public string ServerName { get; }
2025-01-18 18:20:11 +03:00
public string IconPath { get; set; } = "/Assets/svg/folder.svg";
2025-01-11 20:39:58 +03:00
public ContentEntry? Parent { get; private set; }
public bool IsRoot => Parent == null;
public IReadOnlyDictionary<string, ContentEntry> Childs => _childs.ToFrozenDictionary();
public Stream Open()
{
_fileApi.TryOpen(Item!.Value, out var stream);
return stream!;
}
public bool TryOpen([NotNullWhen(true)] out Stream? stream,[NotNullWhen(true)] out RobustManifestItem? item){
stream = null;
item = null;
if(Item is null || !_fileApi.TryOpen(Item.Value, out stream))
return false;
item = Item;
return true;
}
2025-01-14 22:10:16 +03:00
public bool TryGetChild(string name, [NotNullWhen(true)] out ContentEntry? child)
2025-01-11 20:39:58 +03:00
{
return _childs.TryGetValue(name, out child);
}
public bool TryAddChild(ContentEntry contentEntry)
{
2025-01-14 22:10:16 +03:00
if (_childs.TryAdd(contentEntry.PathName, contentEntry))
2025-01-11 20:39:58 +03:00
{
contentEntry.Parent = this;
return true;
}
return false;
}
public ContentPath GetPath()
{
if (Parent != null)
{
var path = Parent.GetPath();
2025-01-12 22:49:32 +03:00
path.Pathes.Add(PathName);
2025-01-11 20:39:58 +03:00
return path;
}
2025-01-14 22:10:16 +03:00
2025-01-12 22:49:32 +03:00
return new ContentPath([PathName]);
2025-01-11 20:39:58 +03:00
}
public ContentEntry GetOrCreateDirectory(ContentPath rootPath)
{
if (rootPath.Pathes.Count == 0) return this;
2025-01-12 22:49:32 +03:00
var fName = rootPath.GetNext();
2025-01-14 22:10:16 +03:00
if (!TryGetChild(fName, out var child))
2025-01-11 20:39:58 +03:00
{
child = new ContentEntry(_viewModel, fName, fName, ServerName, _fileApi);
2025-01-11 20:39:58 +03:00
TryAddChild(child);
}
return child.GetOrCreateDirectory(rootPath);
}
public ContentEntry GetRoot()
{
if (Parent == null) return this;
return Parent.GetRoot();
}
public ContentEntry CreateItem(ContentPath path, RobustManifestItem item, HashApi fileApi)
2025-01-11 20:39:58 +03:00
{
var dir = path.GetDirectory();
var dirEntry = GetOrCreateDirectory(dir);
2025-01-12 22:49:32 +03:00
var name = path.GetName();
var entry = new ContentEntry(_viewModel, name, name, ServerName, fileApi)
2025-01-11 20:39:58 +03:00
{
Item = item
};
2025-01-14 22:10:16 +03:00
2025-01-11 20:39:58 +03:00
dirEntry.TryAddChild(entry);
2025-01-18 18:20:11 +03:00
entry.IconPath = "/Assets/svg/file.svg";
2025-01-11 20:39:58 +03:00
return entry;
}
public bool TryGetEntry(ContentPath path, out ContentEntry? entry)
{
entry = null;
2025-01-14 22:10:16 +03:00
2025-01-11 20:39:58 +03:00
if (path.Pathes.Count == 0)
{
entry = this;
return true;
}
2025-01-12 22:49:32 +03:00
var fName = path.GetNext();
2025-01-14 22:10:16 +03:00
if (!TryGetChild(fName, out var child)) return false;
2025-01-11 20:39:58 +03:00
return child.TryGetEntry(path, out entry);
}
public void OnPathGo()
{
_viewModel.Go(GetPath());
}
2025-01-10 12:28:29 +03:00
}
2025-01-11 20:39:58 +03:00
public struct ContentPath
2025-01-10 12:28:29 +03:00
{
2025-01-14 22:10:16 +03:00
public List<string> Pathes { get; }
2025-01-11 20:39:58 +03:00
public ContentPath(List<string> pathes)
{
2025-01-14 22:10:16 +03:00
Pathes = pathes;
2025-01-11 20:39:58 +03:00
}
public ContentPath(string path)
{
2025-01-12 22:49:32 +03:00
Pathes = string.IsNullOrEmpty(path)
? new List<string>()
: path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).ToList();
2025-01-11 20:39:58 +03:00
}
public ContentPath GetDirectory()
{
2025-01-12 22:49:32 +03:00
if (Pathes.Count == 0)
return this; // Root remains root when getting the directory.
var directoryPathes = Pathes.Take(Pathes.Count - 1).ToList();
return new ContentPath(directoryPathes);
2025-01-11 20:39:58 +03:00
}
public string GetName()
{
2025-01-12 22:49:32 +03:00
if (Pathes.Count == 0)
throw new InvalidOperationException("Cannot get the name of the root path.");
2025-01-11 20:39:58 +03:00
return Pathes.Last();
}
2025-01-12 22:49:32 +03:00
public string GetNext()
{
if (Pathes.Count == 0)
throw new InvalidOperationException("No elements left to retrieve from the root.");
var nextName = Pathes[0];
Pathes.RemoveAt(0);
return string.IsNullOrWhiteSpace(nextName) ? GetNext() : nextName;
}
2025-01-12 15:15:01 +03:00
public ContentPath Clone()
{
2025-01-12 22:49:32 +03:00
return new ContentPath(new List<string>(Pathes));
2025-01-12 15:15:01 +03:00
}
2025-01-12 22:49:32 +03:00
public string Path => Pathes.Count == 0 ? "/" : string.Join("/", Pathes);
public override string ToString()
{
return Path;
}
}