// // PacketLogDocumentViewModel.cs // // Copyright (c) František Boháček. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Dock.Model.Mvvm.Controls; using DynamicData.Binding; using NosSmooth.Comms.Data.Messages; using NosSmooth.Comms.Local; using NosSmooth.Core.Contracts; using NosSmooth.Core.Extensions; using NosSmooth.Core.Stateful; using PacketLogger.Models; using PacketLogger.Models.Packets; using ReactiveUI; namespace PacketLogger.ViewModels; /// public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged { private readonly CommsInjector _injector; private readonly NostaleProcesses _processes; private CancellationTokenSource _ctSource; /// /// Initializes a new instance of the class. /// /// The injector. /// The repository. /// The NosTale processes collection. public PacketLogDocumentViewModel(CommsInjector injector, StatefulRepository repository, NostaleProcesses processes) { _ctSource = new CancellationTokenSource(); _injector = injector; _processes = processes; OpenDummy = ReactiveCommand.CreateFromTask ( () => Task.Run ( () => { Loading = true; Name = "Dummy"; LogViewModel = new LogTabViewModel(new DummyPacketProvider()); Loaded = true; } ) ); OpenFile = ReactiveCommand.CreateFromTask ( async () => { var mainWindow = (App.Current!.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime) ?.MainWindow; var result = await new OpenFileDialog() { AllowMultiple = false, InitialFileName = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName }.ShowAsync(mainWindow!); if (result is null || result.Length == 0) { return; } Loading = true; var path = result[0]; var provider = new FilePacketProvider(path); var openResult = await provider.Open(); if (!openResult.IsSuccess) { Console.WriteLine("Could not open the file."); return; } Title = Path.GetFileName(path); LogViewModel = new LogTabViewModel(provider); Loaded = true; Loading = false; } ); OpenProcess = ReactiveCommand.CreateFromTask ( async (process, ct) => { Loading = true; var connectionResult = await injector.EstablishNamedPipesConnectionAsync (process.Process, _ctSource.Token, ct); if (!connectionResult.IsDefined(out var connection)) { Console.WriteLine(connectionResult.ToFullString()); return; } var provider = new CommsPacketProvider(connection); repository.SetEntity(connection.Client, provider); var contractResult = await connection.Connection.ContractHanshake (new HandshakeRequest("PacketLogger", true, false)) .WaitForAsync(DefaultStates.ResponseObtained, ct: ct); if (!contractResult.IsDefined(out var handshakeResponse)) { repository.Remove(connection.Client); Console.WriteLine(contractResult.ToFullString()); return; } process.WhenPropertyChanged(x => x.CharacterString) .ObserveOn(RxApp.MainThreadScheduler) .Do ( _ => { Title = (process.BrowserManager.IsInGame ? process.BrowserManager.PlayerManager.Player.Name : null) ?? $"Not in game ({process.Process.Id})"; } ) .Subscribe(); LogViewModel = new LogTabViewModel(provider); Title = handshakeResponse.CharacterName ?? $"Not in game ({process.Process.Id})"; Loading = false; Loaded = true; } ); } /// /// Gets the processes observable. /// public ObservableCollection Processes => _processes.Processes; /// /// Gets or sets the name of the tab. /// public string Name { get; set; } = "New tab"; /// /// Gets whether the document is currently being loaded. /// public bool Loading { get; private set; } /// /// Gets whether a document has been loaded. /// public bool Loaded { get; private set; } /// /// Gets the log tab view model. /// public LogTabViewModel? LogViewModel { get; private set; } /// /// Gets command for opening a dummy. /// public ReactiveCommand OpenDummy { get; } /// /// Gets command for opening a file. /// public ReactiveCommand OpenFile { get; } /// /// Gets the command for opening a process / connecting to a process. /// public ReactiveCommand OpenProcess { get; } }