diff --git a/.idea/.idea.Nebula/.idea/avalonia.xml b/.idea/.idea.Nebula/.idea/avalonia.xml index 21eb961..99693f9 100644 --- a/.idea/.idea.Nebula/.idea/avalonia.xml +++ b/.idea/.idea.Nebula/.idea/avalonia.xml @@ -15,7 +15,9 @@ + + diff --git a/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs b/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs index e93868f..c771660 100644 --- a/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs +++ b/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs @@ -80,18 +80,19 @@ public partial class AccountInfoViewModel : ViewModelBase public async void DoAuth() { - _popupMessageService.Popup("Auth think, please wait..."); + var message = GetViewModel(); + message.InfoText = "Auth think, please wait..."; + _popupMessageService.Popup(message); if(await _authService.Auth(CurrentAlp)) { - _popupMessageService.ClosePopup(); - _popupMessageService.Popup("Hello, " + _authService.SelectedAuth!.AuthLoginPassword.Login); + message.Dispose(); IsLogged = true; _configurationService.SetConfigValue(CurrentConVar.AuthCurrent, CurrentAlp); } else { - _popupMessageService.ClosePopup(); + message.Dispose(); Logout(); _popupMessageService.Popup("Well, shit is happened: " + _authService.Reason); } @@ -100,7 +101,7 @@ public partial class AccountInfoViewModel : ViewModelBase public void Logout() { IsLogged = false; - CurrentAlp = new AuthLoginPassword("", "", ""); + //CurrentAlp = new AuthLoginPassword("", "", ""); _authService.ClearAuth(); } diff --git a/Nebula.Launcher/ViewModels/ExceptionViewModel.cs b/Nebula.Launcher/ViewModels/ExceptionViewModel.cs new file mode 100644 index 0000000..eed2337 --- /dev/null +++ b/Nebula.Launcher/ViewModels/ExceptionViewModel.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.ObjectModel; +using Nebula.Launcher.ViewHelper; +using Nebula.Launcher.Views.Popup; + +namespace Nebula.Launcher.ViewModels; + +[ViewModelRegister(typeof(ExceptionView), false)] +public class ExceptionViewModel : PopupViewModelBase +{ + public ExceptionViewModel() : base() + { + var e = new Exception("TEST"); + + AppendError(e); + } + + public ExceptionViewModel(IServiceProvider serviceProvider) : base(serviceProvider){} + + public override string Title => "Oopsie! Some shit is happened now!"; + + public ObservableCollection Errors { get; } = new(); + + public void AppendError(Exception exception) + { + Errors.Add(exception); + if(exception.InnerException != null) + AppendError(exception.InnerException); + } +} diff --git a/Nebula.Launcher/ViewModels/LoadingContextViewModel.cs b/Nebula.Launcher/ViewModels/LoadingContextViewModel.cs new file mode 100644 index 0000000..69a4fdd --- /dev/null +++ b/Nebula.Launcher/ViewModels/LoadingContextViewModel.cs @@ -0,0 +1,44 @@ +using System; +using CommunityToolkit.Mvvm.ComponentModel; +using Nebula.Launcher.ViewHelper; +using Nebula.Launcher.Views.Popup; +using Nebula.Shared.Models; + +namespace Nebula.Launcher.ViewModels; + +[ViewModelRegister(typeof(LoadingContextView), false)] +public sealed partial class LoadingContextViewModel : PopupViewModelBase, ILoadingHandler +{ + public LoadingContextViewModel() :base(){} + public LoadingContextViewModel(IServiceProvider provider) : base(provider){} + + public string LoadingName { get; set; } = "Loading..."; + + public override string Title => LoadingName; + + [ObservableProperty] + private int _currJobs; + [ObservableProperty] + private int _resolvedJobs; + + public void SetJobsCount(int count) + { + CurrJobs = count; + } + + public int GetJobsCount() + { + return CurrJobs; + } + + public void SetResolvedJobsCount(int count) + { + ResolvedJobs = count; + + } + + public int GetResolvedJobsCount() + { + return ResolvedJobs; + } +} \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/MainViewModel.cs b/Nebula.Launcher/ViewModels/MainViewModel.cs index 03b12c7..90684a9 100644 --- a/Nebula.Launcher/ViewModels/MainViewModel.cs +++ b/Nebula.Launcher/ViewModels/MainViewModel.cs @@ -27,19 +27,20 @@ public partial class MainViewModel : ViewModelBase } [UsedImplicitly] - public MainViewModel(AccountInfoViewModel accountInfoViewModel, PopupMessageService popupMessageService, + public MainViewModel(AccountInfoViewModel accountInfoViewModel,DebugService debugService, PopupMessageService popupMessageService, IServiceProvider serviceProvider): base(serviceProvider) { _currentPage = accountInfoViewModel; - _popupMessageService = popupMessageService; + _debugService = debugService; Items = new ObservableCollection(_templates); - _popupMessageService.OnPopupRequired += OnPopupRequired; + popupMessageService.OnPopupRequired += OnPopupRequired; + popupMessageService.OnCloseRequired += OnPopupCloseRequired; SelectedListItem = Items.First(vm => vm.ModelType == typeof(AccountInfoViewModel)); } - - private readonly Queue _viewQueue = new(); + + private readonly List _viewQueue = new(); private readonly List _templates = [ @@ -53,7 +54,7 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private ViewModelBase _currentPage; - private readonly PopupMessageService _popupMessageService; + private readonly DebugService _debugService; [ObservableProperty] private bool _isEnabled = true; [ObservableProperty] private bool _popup; @@ -90,7 +91,7 @@ public partial class MainViewModel : ViewModelBase } else { - _viewQueue.Enqueue(viewModelBase); + _viewQueue.Add(viewModelBase); } } @@ -111,13 +112,10 @@ public partial class MainViewModel : ViewModelBase Helper.OpenBrowser("https://cinka.ru/nebula-launcher/"); } - private void OnPopupRequired(object? viewModelBase) + private void OnPopupRequired(object viewModelBase) { switch (viewModelBase) { - case null: - ClosePopup(); - break; case string str: { var view = GetViewModel(); @@ -128,8 +126,28 @@ public partial class MainViewModel : ViewModelBase case PopupViewModelBase @base: PopupMessage(@base); break; + case Exception error: + var err = GetViewModel(); + _debugService.Error(error); + err.AppendError(error); + PopupMessage(err); + break; } } + + private void OnPopupCloseRequired(object obj) + { + if(obj is not PopupViewModelBase viewModelBase) + { + return; + } + + if (obj == CurrentPopup) + ClosePopup(); + else + _viewQueue.Remove(viewModelBase); + } + [RelayCommand] private void TriggerPane() @@ -140,10 +158,14 @@ public partial class MainViewModel : ViewModelBase [RelayCommand] public void ClosePopup() { - if (!_viewQueue.TryDequeue(out var viewModelBase)) + var viewModelBase = _viewQueue.FirstOrDefault(); + if (viewModelBase is null) OnCloseRequired(); else + { CurrentTitle = viewModelBase.Title; + _viewQueue.RemoveAt(0); + } CurrentPopup = viewModelBase; } diff --git a/Nebula.Launcher/ViewModels/PopupViewModelBase.cs b/Nebula.Launcher/ViewModels/PopupViewModelBase.cs index 32239f6..cac33a6 100644 --- a/Nebula.Launcher/ViewModels/PopupViewModelBase.cs +++ b/Nebula.Launcher/ViewModels/PopupViewModelBase.cs @@ -1,16 +1,25 @@ using System; +using Microsoft.Extensions.DependencyInjection; +using Nebula.Shared.Services; namespace Nebula.Launcher.ViewModels; -public abstract class PopupViewModelBase : ViewModelBase +public abstract class PopupViewModelBase : ViewModelBase, IDisposable { + private readonly IServiceProvider _serviceProvider; + public PopupViewModelBase() { } public PopupViewModelBase(IServiceProvider serviceProvider) : base(serviceProvider) { + _serviceProvider = serviceProvider; } - public abstract string Title { get; } + public abstract string Title { get; } + public void Dispose() + { + _serviceProvider.GetService()?.ClosePopup(this); + } } \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs index 650d13a..5419db8 100644 --- a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs +++ b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; using Avalonia.Media; using CommunityToolkit.Mvvm.ComponentModel; using Nebula.Launcher.ViewHelper; @@ -22,12 +23,23 @@ public partial class ServerEntryModelView : ViewModelBase private readonly RunnerService _runnerService = default!; private readonly PopupMessageService _popupMessageService; - [ObservableProperty] private bool _runVisible = true; + public bool RunVisible => Process == null; public ServerHubInfo ServerHubInfo { get; set; } = default!; - - - private Process? _process; + + + private Process? _p; + private Process? Process + { + get { return _p; } + set + { + _p = value; + OnPropertyChanged(nameof(RunVisible)); + } + } + + public LogPopupModelView CurrLog; @@ -55,65 +67,93 @@ public partial class ServerEntryModelView : ViewModelBase CurrLog = GetViewModel(); } - public async void RunInstance() + public void RunInstance() { - var authProv = _authService.SelectedAuth; - - var buildInfo = await _contentService.GetBuildInfo(new RobustUrl(ServerHubInfo.Address), _cancellationService.Token); + Task.Run(RunAsync); + } - await _runnerService.PrepareRun(buildInfo, _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 - }); - - - if (_process is null) - { - return; - } - - _process.BeginOutputReadLine(); - _process.BeginErrorReadLine(); - - RunVisible = false; - - _process.OutputDataReceived += OnOutputDataReceived; - _process.ErrorDataReceived += OnErrorDataReceived; - - _process.Exited += OnExited; + public async Task RunAsync() + { + try + { + var authProv = _authService.SelectedAuth; + + var buildInfo = + await _contentService.GetBuildInfo(new RobustUrl(ServerHubInfo.Address), _cancellationService.Token); + + using (var loadingContext = GetViewModel()) + { + loadingContext.LoadingName = "Loading instance..."; + ((ILoadingHandler)loadingContext).AppendJob(); + + _popupMessageService.Popup(loadingContext); + + + 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 e) + { + _popupMessageService.Popup("Task canceled"); + } + catch (Exception e) + { + _popupMessageService.Popup(e); + } } private void OnExited(object? sender, EventArgs e) { - if (_process is null) + if (Process is null) { return; } - _process.OutputDataReceived -= OnOutputDataReceived; - _process.ErrorDataReceived -= OnErrorDataReceived; - _process.Exited -= OnExited; + 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; - RunVisible = true; + Process.Dispose(); + Process = null; } private void OnErrorDataReceived(object sender, DataReceivedEventArgs e) @@ -142,7 +182,7 @@ public partial class ServerEntryModelView : ViewModelBase public void StopInstance() { - _process?.Close(); + Process?.CloseMainWindow(); } static string FindDotnetPath() diff --git a/Nebula.Launcher/Views/Popup/ExceptionView.axaml b/Nebula.Launcher/Views/Popup/ExceptionView.axaml new file mode 100644 index 0000000..3ef54ec --- /dev/null +++ b/Nebula.Launcher/Views/Popup/ExceptionView.axaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Nebula.Launcher/Views/Popup/ExceptionView.axaml.cs b/Nebula.Launcher/Views/Popup/ExceptionView.axaml.cs new file mode 100644 index 0000000..2b64a54 --- /dev/null +++ b/Nebula.Launcher/Views/Popup/ExceptionView.axaml.cs @@ -0,0 +1,20 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Nebula.Launcher.ViewModels; + +namespace Nebula.Launcher.Views.Popup; + +public partial class ExceptionView : UserControl +{ + public ExceptionView() + { + InitializeComponent(); + } + + public ExceptionView(ExceptionViewModel viewModel) : this() + { + DataContext = viewModel; + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Views/Popup/LoadingContextView.axaml b/Nebula.Launcher/Views/Popup/LoadingContextView.axaml new file mode 100644 index 0000000..6aac85c --- /dev/null +++ b/Nebula.Launcher/Views/Popup/LoadingContextView.axaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/Nebula.Launcher/Views/Popup/LoadingContextView.axaml.cs b/Nebula.Launcher/Views/Popup/LoadingContextView.axaml.cs new file mode 100644 index 0000000..a4a3231 --- /dev/null +++ b/Nebula.Launcher/Views/Popup/LoadingContextView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Nebula.Launcher.ViewModels; + +namespace Nebula.Launcher.Views.Popup; + +public partial class LoadingContextView : UserControl +{ + public LoadingContextView() + { + InitializeComponent(); + } + + public LoadingContextView(LoadingContextViewModel viewModel): this() + { + DataContext = viewModel; + } +} \ No newline at end of file diff --git a/Nebula.Runner/App.cs b/Nebula.Runner/App.cs index 888cf41..908d4d3 100644 --- a/Nebula.Runner/App.cs +++ b/Nebula.Runner/App.cs @@ -47,11 +47,80 @@ public sealed class App(DebugService debugService, RunnerService runnerService, args.Add("--ss14-address"); args.Add(url.ToString()); - await runnerService.Run(args.ToArray(), buildInfo, this, cancelTokenSource.Token); + await runnerService.Run(args.ToArray(), buildInfo, this, new ConsoleLoadingHandler(), cancelTokenSource.Token); } public void Redial(Uri uri, string text = "") { + } +} + +public sealed class ConsoleLoadingHandler : ILoadingHandler +{ + private int _currJobs; + private int _resolvedJobs; + + private float _percent = 0f; + + public void SetJobsCount(int count) + { + _currJobs = count; + + UpdatePercent(); + Draw(); + } + + public int GetJobsCount() + { + return _currJobs; + } + + public void SetResolvedJobsCount(int count) + { + _resolvedJobs = count; + + UpdatePercent(); + Draw(); + } + + public int GetResolvedJobsCount() + { + return _resolvedJobs; + } + + private void UpdatePercent() + { + if(_currJobs == 0) + { + _percent = 0; + return; + } + + if(_resolvedJobs > _currJobs) return; + + _percent = _resolvedJobs /(float) _currJobs; + } + + private void Draw() + { + var barCount = 10; + var fullCount = (int)(barCount * _percent); + var emptyCount = barCount - fullCount; + + Console.Write("\r"); + + for (var i = 0; i < fullCount; i++) + { + Console.Write("#"); + } + + for (var i = 0; i < emptyCount; i++) + { + Console.Write(" "); + } + + Console.Write($"\t {_resolvedJobs}/{_currJobs}"); + } } \ No newline at end of file diff --git a/Nebula.Shared/FileApis/FileApi.cs b/Nebula.Shared/FileApis/FileApi.cs index 08187a0..d876851 100644 --- a/Nebula.Shared/FileApis/FileApi.cs +++ b/Nebula.Shared/FileApis/FileApi.cs @@ -2,7 +2,7 @@ namespace Nebula.Shared.FileApis; -public class FileApi : IReadWriteFileApi +public sealed class FileApi : IReadWriteFileApi { public string RootPath; @@ -13,10 +13,19 @@ public class FileApi : IReadWriteFileApi public bool TryOpen(string path, out Stream? stream) { - if (File.Exists(Path.Join(RootPath, path))) + var fullPath = Path.Join(RootPath, path); + if (File.Exists(fullPath)) { - stream = File.OpenRead(Path.Join(RootPath, path)); - return true; + try + { + stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + return true; + } + catch + { + stream = null; + return false; + } } stream = null; @@ -27,27 +36,43 @@ public class FileApi : IReadWriteFileApi { var currPath = Path.Join(RootPath, path); - var dirInfo = new DirectoryInfo(Path.GetDirectoryName(currPath)); - if (!dirInfo.Exists) dirInfo.Create(); + try + { + var dirInfo = new DirectoryInfo(Path.GetDirectoryName(currPath) ?? throw new InvalidOperationException()); + if (!dirInfo.Exists) dirInfo.Create(); - using var stream = File.OpenWrite(currPath); - input.CopyTo(stream); - stream.Flush(); - stream.Close(); - return true; + using var stream = new FileStream(currPath, FileMode.Create, FileAccess.Write, FileShare.None); + input.CopyTo(stream); + return true; + } + catch + { + return false; + } } public bool Remove(string path) { - if (!Has(path)) return false; - File.Delete(Path.Join(RootPath, path)); - return true; + var fullPath = Path.Join(RootPath, path); + try + { + if (File.Exists(fullPath)) + { + File.Delete(fullPath); + return true; + } + } + catch + { + // Log exception if necessary + } + return false; } public bool Has(string path) { - var currPath = Path.Join(RootPath, path); - return File.Exists(currPath); + var fullPath = Path.Join(RootPath, path); + return File.Exists(fullPath); } public IEnumerable AllFiles => Directory.EnumerateFiles(RootPath, "*.*", SearchOption.AllDirectories); diff --git a/Nebula.Shared/Models/ILoadingHandler.cs b/Nebula.Shared/Models/ILoadingHandler.cs new file mode 100644 index 0000000..b5ccae2 --- /dev/null +++ b/Nebula.Shared/Models/ILoadingHandler.cs @@ -0,0 +1,47 @@ +namespace Nebula.Shared.Models; + +public interface ILoadingHandler +{ + public void SetJobsCount(int count); + public int GetJobsCount(); + + public void SetResolvedJobsCount(int count); + public int GetResolvedJobsCount(); + + public void AppendJob(int count = 1) + { + SetJobsCount(GetJobsCount() + count); + } + + public void AppendResolvedJob(int count = 1) + { + SetResolvedJobsCount(GetResolvedJobsCount() + count); + } + + public void Clear() + { + SetResolvedJobsCount(0); + SetJobsCount(0); + } + + public QueryJob GetQueryJob() + { + return new QueryJob(this); + } +} + +public sealed class QueryJob: IDisposable +{ + private readonly ILoadingHandler _handler; + + public QueryJob(ILoadingHandler handler) + { + _handler = handler; + handler.AppendJob(); + } + + public void Dispose() + { + _handler.AppendResolvedJob(); + } +} \ No newline at end of file diff --git a/Nebula.Shared/Services/ContentService.Download.cs b/Nebula.Shared/Services/ContentService.Download.cs index 5700dfb..54462e8 100644 --- a/Nebula.Shared/Services/ContentService.Download.cs +++ b/Nebula.Shared/Services/ContentService.Download.cs @@ -15,7 +15,7 @@ public partial class ContentService return fileService.ContentFileApi.Has(item.Hash); } - public async Task> EnsureItems(ManifestReader manifestReader, Uri downloadUri, + public async Task> EnsureItems(ManifestReader manifestReader, Uri downloadUri, ILoadingHandler loadingHandler, CancellationToken cancellationToken) { List allItems = []; @@ -36,14 +36,14 @@ public partial class ContentService debugService.Log("Download Count:" + items.Count); - await Download(downloadUri, items, cancellationToken); + await Download(downloadUri, items, loadingHandler, cancellationToken); fileService.ManifestItems = allItems; return allItems; } - public async Task> EnsureItems(RobustManifestInfo info, + public async Task> EnsureItems(RobustManifestInfo info, ILoadingHandler loadingHandler, CancellationToken cancellationToken) { debugService.Log("Getting manifest: " + info.Hash); @@ -51,7 +51,7 @@ public partial class ContentService if (fileService.ManifestFileApi.TryOpen(info.Hash, out var stream)) { debugService.Log("Loading manifest from: " + info.Hash); - return await EnsureItems(new ManifestReader(stream), info.DownloadUri, cancellationToken); + return await EnsureItems(new ManifestReader(stream), info.DownloadUri, loadingHandler, cancellationToken); } debugService.Log("Fetching manifest from: " + info.ManifestUri); @@ -63,14 +63,16 @@ public partial class ContentService fileService.ManifestFileApi.Save(info.Hash, streamContent); streamContent.Seek(0, SeekOrigin.Begin); using var manifestReader = new ManifestReader(streamContent); - return await EnsureItems(manifestReader, info.DownloadUri, cancellationToken); + return await EnsureItems(manifestReader, info.DownloadUri, loadingHandler, cancellationToken); } - public async Task Unpack(RobustManifestInfo info, IWriteFileApi otherApi, CancellationToken cancellationToken) + public async Task Unpack(RobustManifestInfo info, IWriteFileApi otherApi, ILoadingHandler loadingHandler, CancellationToken cancellationToken) { debugService.Log("Unpack manifest files"); - var items = await EnsureItems(info, cancellationToken); + var items = await EnsureItems(info, loadingHandler, cancellationToken); + loadingHandler.AppendJob(items.Count); foreach (var item in items) + { if (fileService.ContentFileApi.TryOpen(item.Hash, out var stream)) { debugService.Log($"Unpack {item.Hash} to: {item.Path}"); @@ -81,9 +83,11 @@ public partial class ContentService { debugService.Error("OH FUCK!! " + item.Path); } + loadingHandler.AppendResolvedJob(); + } } - public async Task Download(Uri contentCdn, List toDownload, CancellationToken cancellationToken) + public async Task Download(Uri contentCdn, List toDownload, ILoadingHandler loadingHandler, CancellationToken cancellationToken) { if (toDownload.Count == 0 || cancellationToken.IsCancellationRequested) { @@ -91,6 +95,8 @@ public partial class ContentService return; } + var downloadJobWatch = loadingHandler.GetQueryJob(); + debugService.Log("Downloading from: " + contentCdn); var requestBody = new byte[toDownload.Count * 4]; @@ -117,6 +123,8 @@ public partial class ContentService debugService.Log("Downloading is cancelled!"); return; } + + downloadJobWatch.Dispose(); response.EnsureSuccessStatusCode(); @@ -155,6 +163,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) @@ -230,6 +241,7 @@ public partial class ContentService fileService.ContentFileApi.Save(item.Hash, fileStream); debugService.Log("file saved:" + item.Path); + loadingHandler.AppendResolvedJob(); i += 1; } } diff --git a/Nebula.Shared/Services/EngineService.cs b/Nebula.Shared/Services/EngineService.cs index c4a3f58..5c3a9b3 100644 --- a/Nebula.Shared/Services/EngineService.cs +++ b/Nebula.Shared/Services/EngineService.cs @@ -81,13 +81,16 @@ public sealed class EngineService try { - return _assemblyService.Mount(_fileService.OpenZip(version, _fileService.EngineFileApi)); + var api = _fileService.OpenZip(version, _fileService.EngineFileApi); + if (api != null) return _assemblyService.Mount(api); } catch (Exception e) { _fileService.EngineFileApi.Remove(version); throw; } + + return null; } public async Task DownloadEngine(string version) diff --git a/Nebula.Shared/Services/FileService.cs b/Nebula.Shared/Services/FileService.cs index 922f026..08eb196 100644 --- a/Nebula.Shared/Services/FileService.cs +++ b/Nebula.Shared/Services/FileService.cs @@ -51,15 +51,24 @@ public class FileService return new FileApi(Path.Join(RootPath, path)); } - public ZipFileApi OpenZip(string path, IFileApi fileApi) + public ZipFileApi? OpenZip(string path, IFileApi fileApi) { - if (!fileApi.TryOpen(path, out var zipStream)) - return null; + Stream? zipStream = null; + try + { + if (!fileApi.TryOpen(path, out zipStream)) + return null; - var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Read); + var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Read); - var prefix = ""; - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) prefix = "Space Station 14.app/Contents/Resources/"; - return new ZipFileApi(zipArchive, prefix); + var prefix = ""; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) prefix = "Space Station 14.app/Contents/Resources/"; + return new ZipFileApi(zipArchive, prefix); + } + catch (Exception e) + { + zipStream?.Dispose(); + throw; + } } } \ No newline at end of file diff --git a/Nebula.Shared/Services/PopupMessageService.cs b/Nebula.Shared/Services/PopupMessageService.cs index ab4c8d1..0fcd9c0 100644 --- a/Nebula.Shared/Services/PopupMessageService.cs +++ b/Nebula.Shared/Services/PopupMessageService.cs @@ -3,13 +3,14 @@ namespace Nebula.Shared.Services; [ServiceRegister] public class PopupMessageService { - public Action? OnPopupRequired; + public Action? OnPopupRequired; + public Action? OnCloseRequired; public void Popup(object obj) { OnPopupRequired?.Invoke(obj); } - public void ClosePopup() + public void ClosePopup(object obj) { - OnPopupRequired?.Invoke(null); + OnCloseRequired?.Invoke(obj); } } \ No newline at end of file diff --git a/Nebula.Shared/Services/RestService.cs b/Nebula.Shared/Services/RestService.cs index af10388..77cc549 100644 --- a/Nebula.Shared/Services/RestService.cs +++ b/Nebula.Shared/Services/RestService.cs @@ -25,17 +25,8 @@ public class RestService public async Task> GetAsync(Uri uri, CancellationToken cancellationToken) { - _debug.Debug("GET " + uri); - try - { - var response = await _client.GetAsync(uri, cancellationToken); - return await ReadResult(response, cancellationToken); - } - catch (Exception ex) - { - _debug.Error("ERROR WHILE CONNECTION " + uri + ": " + ex.Message); - return new RestResult(default, ex.Message, HttpStatusCode.RequestTimeout); - } + var response = await _client.GetAsync(uri, cancellationToken); + return await ReadResult(response, cancellationToken); } public async Task GetAsyncDefault(Uri uri, T defaultValue, CancellationToken cancellationToken) @@ -46,53 +37,25 @@ public class RestService public async Task> PostAsync(T information, Uri uri, CancellationToken cancellationToken) { - _debug.Debug("POST " + uri); - try - { - var json = JsonSerializer.Serialize(information, _serializerOptions); - var content = new StringContent(json, Encoding.UTF8, "application/json"); - var response = await _client.PostAsync(uri, content, cancellationToken); - return await ReadResult(response, cancellationToken); - } - catch (Exception ex) - { - _debug.Debug("ERROR " + ex.Message); - return new RestResult(default, ex.Message, HttpStatusCode.RequestTimeout); - } + var json = JsonSerializer.Serialize(information, _serializerOptions); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await _client.PostAsync(uri, content, cancellationToken); + return await ReadResult(response, cancellationToken); } public async Task> PostAsync(Stream stream, Uri uri, CancellationToken cancellationToken) { - _debug.Debug("POST " + uri); - try - { - using var multipartFormContent = - new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)); - multipartFormContent.Add(new StreamContent(stream), "formFile", "image.png"); - var response = await _client.PostAsync(uri, multipartFormContent, cancellationToken); - return await ReadResult(response, cancellationToken); - } - catch (Exception ex) - { - _debug.Error("ERROR " + ex.Message); - if (ex.StackTrace != null) _debug.Error(ex.StackTrace); - return new RestResult(default, ex.Message, HttpStatusCode.RequestTimeout); - } + using var multipartFormContent = + new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)); + multipartFormContent.Add(new StreamContent(stream), "formFile", "image.png"); + var response = await _client.PostAsync(uri, multipartFormContent, cancellationToken); + return await ReadResult(response, cancellationToken); } public async Task> DeleteAsync(Uri uri, CancellationToken cancellationToken) { - _debug.Debug("DELETE " + uri); - try - { - var response = await _client.DeleteAsync(uri, cancellationToken); - return await ReadResult(response, cancellationToken); - } - catch (Exception ex) - { - _debug.Debug("ERROR " + ex.Message); - return new RestResult(default, ex.Message, HttpStatusCode.RequestTimeout); - } + var response = await _client.DeleteAsync(uri, cancellationToken); + return await ReadResult(response, cancellationToken); } private async Task> ReadResult(HttpResponseMessage response, CancellationToken cancellationToken) diff --git a/Nebula.Shared/Services/RunnerService.cs b/Nebula.Shared/Services/RunnerService.cs index 751f3d1..8ecce3a 100644 --- a/Nebula.Shared/Services/RunnerService.cs +++ b/Nebula.Shared/Services/RunnerService.cs @@ -12,7 +12,7 @@ public sealed class RunnerService( EngineService engineService, AssemblyService assemblyService) { - public async Task PrepareRun(RobustBuildInfo buildInfo, CancellationToken cancellationToken) + public async Task PrepareRun(RobustBuildInfo buildInfo, ILoadingHandler loadingHandler, CancellationToken cancellationToken) { debugService.Log("Prepare Content!"); @@ -21,11 +21,11 @@ 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, cancellationToken); + 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, + public async Task Run(string[] runArgs, RobustBuildInfo buildInfo, IRedialApi redialApi, ILoadingHandler loadingHandler, CancellationToken cancellationToken) { debugService.Log("Start Content!"); @@ -35,7 +35,7 @@ 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, cancellationToken); + await contentService.EnsureItems(buildInfo.RobustManifestInfo, loadingHandler, cancellationToken); var extraMounts = new List {