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

412 lines
11 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;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
2025-01-10 12:28:29 +03:00
using CommunityToolkit.Mvvm.ComponentModel;
2025-01-11 20:39:58 +03:00
using Microsoft.Extensions.DependencyInjection;
2025-01-10 12:28:29 +03:00
using Nebula.Launcher.ViewHelper;
using Nebula.Launcher.Views.Pages;
2025-01-11 20:39:58 +03:00
using Nebula.Shared;
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
namespace Nebula.Launcher.ViewModels;
[ViewModelRegister(typeof(ContentBrowserView))]
public sealed partial class ContentBrowserViewModel : ViewModelBase
{
2025-01-11 20:39:58 +03:00
private readonly IServiceProvider _provider;
private readonly ContentService _contentService;
private readonly CancellationService _cancellationService;
2025-01-12 15:15:01 +03:00
private readonly FileService _fileService;
2025-01-11 20:39:58 +03:00
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();
2025-01-10 12:28:29 +03:00
[ObservableProperty] private string _message = "";
[ObservableProperty] private string _searchText = "";
2025-01-12 22:49:32 +03:00
[ObservableProperty] private string _serverText = "";
2025-01-10 12:28:29 +03:00
2025-01-11 20:39:58 +03:00
private ContentEntry? _selectedEntry;
public ContentEntry? SelectedEntry
{
get => _selectedEntry;
set
{
2025-01-12 15:15:01 +03:00
if (value is { Item: not null })
{
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))
{
stream.CopyTo(sw);
}
stream.Dispose();
var startInfo = new ProcessStartInfo(myTempFile)
{
UseShellExecute = true
};
Process.Start(startInfo);
}
return;
}
2025-01-11 20:39:58 +03:00
2025-01-12 15:15:01 +03:00
Entries.Clear();
_selectedEntry = value;
if (value == null) return;
2025-01-11 20:39:58 +03:00
2025-01-12 15:15:01 +03:00
foreach (var (_, entryCh) in value.Childs)
2025-01-11 20:39:58 +03:00
{
Entries.Add(entryCh);
}
}
}
2025-01-10 12:28:29 +03:00
public ContentBrowserViewModel() : base()
{
2025-01-12 22:49:32 +03:00
var a = new ContentEntry(this, "A:","A", "");
var b = new ContentEntry(this, "B","B", "");
2025-01-11 20:39:58 +03:00
a.TryAddChild(b);
Entries.Add(a);
}
public ContentBrowserViewModel(IServiceProvider provider, ContentService contentService, CancellationService cancellationService,
FileService fileService, HubService hubService, DebugService debugService, PopupMessageService popupService) : base(provider)
{
_provider = provider;
_contentService = contentService;
_cancellationService = cancellationService;
2025-01-12 15:15:01 +03:00
_fileService = fileService;
2025-01-11 20:39:58 +03:00
_debugService = debugService;
_popupService = popupService;
2025-01-12 15:15:01 +03:00
2025-01-12 22:49:32 +03:00
FillRoot(hubService.ServerList);
2025-01-11 20:39:58 +03:00
hubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
hubService.HubServerLoaded += GoHome;
2025-01-12 15:15:01 +03:00
if (!hubService.IsUpdating)
{
GoHome();
}
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;
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)
{
if(obj.Action == HubServerChangeAction.Clear) _root.Clear();
if (obj.Action == HubServerChangeAction.Add)
{
2025-01-12 22:49:32 +03:00
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));
}
}
2025-01-12 15:15:01 +03:00
public async void Go(ContentPath path, bool appendHistory = true)
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
}
if (string.IsNullOrEmpty(ServerText))
2025-01-11 20:39:58 +03:00
{
SearchText = "";
GoHome();
return;
}
2025-01-12 22:49:32 +03:00
if (ServerText != SelectedEntry?.ServerName)
2025-01-11 20:39:58 +03:00
{
2025-01-12 22:49:32 +03:00
SelectedEntry = await CreateEntry(ServerText);
2025-01-11 20:39:58 +03:00
}
2025-01-13 19:24:12 +03:00
_debugService.Debug("Going to:" + path.Path);
2025-01-10 12:28:29 +03:00
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-11 20:39:58 +03:00
{
2025-01-13 19:24:12 +03:00
throw new Exception("Not found! " + oriPath.Path);
2025-01-11 20:39:58 +03:00
}
2025-01-12 15:15:01 +03:00
if (appendHistory)
{
AppendHistory(SearchText);
}
SearchText = oriPath.Path;
2025-01-11 20:39:58 +03:00
2025-01-12 15:15:01 +03:00
SelectedEntry = centry;
2025-01-11 20:39:58 +03:00
}
catch (Exception e)
{
Console.WriteLine(e);
2025-01-12 15:15:01 +03:00
SearchText = oriPath.Path;
2025-01-11 20:39:58 +03:00
_popupService.Popup(e);
}
}
public void OnBackEnter()
{
2025-01-12 15:15:01 +03:00
Go(new ContentPath(GetHistory()), false);
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)
{
var rurl = serverUrl.ToRobustUrl();
var info = await _contentService.GetBuildInfo(rurl, _cancellationService.Token);
var loading = _provider.GetService<LoadingContextViewModel>()!;
loading.LoadingName = "Loading entry";
_popupService.Popup(loading);
var items = await _contentService.EnsureItems(info.RobustManifestInfo, loading,
_cancellationService.Token);
2025-01-12 22:49:32 +03:00
var rootEntry = new ContentEntry(this,"","", serverUrl);
2025-01-11 20:39:58 +03:00
foreach (var item in items)
{
var path = new ContentPath(item.Path);
rootEntry.CreateItem(path, item);
}
2025-01-10 12:28:29 +03:00
2025-01-11 20:39:58 +03:00
loading.Dispose();
return rootEntry;
}
private void AppendHistory(string str)
{
if(_history.Count >= 10) _history.RemoveAt(9);
_history.Insert(0, str);
}
private string GetHistory()
{
if (_history.Count == 0) return "";
var h = _history[0];
_history.RemoveAt(0);
return h;
}
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-11 20:39:58 +03:00
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; }
2025-01-12 22:49:32 +03:00
public string PathName { get; private set; }
2025-01-11 20:39:58 +03:00
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();
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)
{
2025-01-12 22:49:32 +03:00
if(_childs.TryAdd(contentEntry.PathName, contentEntry))
2025-01-11 20:39:58 +03:00
{
contentEntry.Parent = this;
return true;
}
return false;
}
2025-01-12 22:49:32 +03:00
internal ContentEntry(ContentBrowserViewModel viewModel, string name, string pathName, string serverName)
2025-01-11 20:39:58 +03:00
{
Name = name;
ServerName = serverName;
2025-01-12 22:49:32 +03:00
PathName = pathName;
2025-01-11 20:39:58 +03:00
_viewModel = viewModel;
}
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-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-11 20:39:58 +03:00
if(!TryGetChild(fName, out var child))
{
2025-01-12 22:49:32 +03:00
child = new ContentEntry(_viewModel, fName, fName, ServerName);
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)
{
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)
2025-01-11 20:39:58 +03:00
{
Item = item
};
dirEntry.TryAddChild(entry);
entry.IconPath = IconImage;
return entry;
}
public bool TryGetEntry(ContentPath path, out ContentEntry? entry)
{
entry = null;
if (path.Pathes.Count == 0)
{
entry = this;
return true;
}
2025-01-12 22:49:32 +03:00
var fName = path.GetNext();
2025-01-11 20:39:58 +03:00
if(!TryGetChild(fName, out var child))
{
return false;
}
return child.TryGetEntry(path, out entry);
}
public void OnPathGo()
{
_viewModel.Go(GetPath());
}
2025-01-10 12:28:29 +03:00
}
2025-01-12 22:49:32 +03:00
2025-01-11 20:39:58 +03:00
public struct ContentPath
2025-01-10 12:28:29 +03:00
{
2025-01-12 22:49:32 +03:00
public List<string> Pathes { get; private set; }
2025-01-11 20:39:58 +03:00
public ContentPath(List<string> pathes)
{
2025-01-12 22:49:32 +03:00
Pathes = pathes ?? new List<string>();
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;
}
}