From aa49f2c91c461ef4866d076faaccf0148f10ab6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Fri, 3 Feb 2023 16:40:29 +0100 Subject: [PATCH] feat: add basic packet sender --- .../Models/Packets/CommsPacketProvider.cs | 5 +- .../Models/Packets/DummyPacketProvider.cs | 41 +++++- .../Models/Packets/FilePacketProvider.cs | 25 +++- .../Models/Packets/IPacketProvider.cs | 22 +++ .../Models/Packets/IPacketSender.cs | 34 ----- src/PacketLogger/ViewModels/DockFactory.cs | 67 +++++++-- ...umentViewModel.cs => DocumentViewModel.cs} | 71 ++++++++-- .../ViewModels/MainWindowViewModel.cs | 57 ++++++-- ...ewModel.cs => PacketLogFilterViewModel.cs} | 8 +- ...gTabViewModel.cs => PacketLogViewModel.cs} | 18 +-- .../ViewModels/PacketSendSubViewModel.cs | 130 ++++++++++++++++++ .../ViewModels/PacketSenderViewModel.cs | 44 ++++++ ...gDocumentView.axaml => DocumentView.axaml} | 25 +++- ...entView.axaml.cs => DocumentView.axaml.cs} | 8 +- src/PacketLogger/Views/MainWindow.axaml | 10 +- ...abView.axaml => PacketLogFilterView.axaml} | 4 +- ....axaml.cs => PacketLogFilterView.axaml.cs} | 8 +- .../{LogTabView.axaml => PacketLogView.axaml} | 4 +- ...abView.axaml.cs => PacketLogView.axaml.cs} | 8 +- .../Views/PacketSendSubView.axaml | 30 ++++ .../Views/PacketSendSubView.axaml.cs | 29 ++++ src/PacketLogger/Views/PacketSenderView.axaml | 17 +++ .../Views/PacketSenderView.axaml.cs | 29 ++++ 23 files changed, 588 insertions(+), 106 deletions(-) delete mode 100644 src/PacketLogger/Models/Packets/IPacketSender.cs rename src/PacketLogger/ViewModels/{PacketLogDocumentViewModel.cs => DocumentViewModel.cs} (70%) rename src/PacketLogger/ViewModels/{LogFilterTabViewModel.cs => PacketLogFilterViewModel.cs} (91%) rename src/PacketLogger/ViewModels/{LogTabViewModel.cs => PacketLogViewModel.cs} (91%) create mode 100644 src/PacketLogger/ViewModels/PacketSendSubViewModel.cs create mode 100644 src/PacketLogger/ViewModels/PacketSenderViewModel.cs rename src/PacketLogger/Views/{PacketLogDocumentView.axaml => DocumentView.axaml} (78%) rename src/PacketLogger/Views/{PacketLogDocumentView.axaml.cs => DocumentView.axaml.cs} (69%) rename src/PacketLogger/Views/{LogFilterTabView.axaml => PacketLogFilterView.axaml} (95%) rename src/PacketLogger/Views/{LogFilterTabView.axaml.cs => PacketLogFilterView.axaml.cs} (75%) rename src/PacketLogger/Views/{LogTabView.axaml => PacketLogView.axaml} (98%) rename src/PacketLogger/Views/{LogTabView.axaml.cs => PacketLogView.axaml.cs} (81%) create mode 100644 src/PacketLogger/Views/PacketSendSubView.axaml create mode 100644 src/PacketLogger/Views/PacketSendSubView.axaml.cs create mode 100644 src/PacketLogger/Views/PacketSenderView.axaml create mode 100644 src/PacketLogger/Views/PacketSenderView.axaml.cs diff --git a/src/PacketLogger/Models/Packets/CommsPacketProvider.cs b/src/PacketLogger/Models/Packets/CommsPacketProvider.cs index 453b2df..6d527d1 100644 --- a/src/PacketLogger/Models/Packets/CommsPacketProvider.cs +++ b/src/PacketLogger/Models/Packets/CommsPacketProvider.cs @@ -20,7 +20,7 @@ namespace PacketLogger.Models.Packets; /// /// A packet provider using a connection to a nostale client. /// -public class CommsPacketProvider : IPacketSender +public class CommsPacketProvider : IPacketProvider { private readonly Comms _comms; private long _currentIndex; @@ -38,6 +38,9 @@ public class CommsPacketProvider : IPacketSender /// public event PropertyChangedEventHandler? PropertyChanged; + /// + public string Name => "TODO"; + /// public bool IsOpen => _comms.Connection.Connection.State == ConnectionState.Open; diff --git a/src/PacketLogger/Models/Packets/DummyPacketProvider.cs b/src/PacketLogger/Models/Packets/DummyPacketProvider.cs index 16d7c5c..ad15b3a 100644 --- a/src/PacketLogger/Models/Packets/DummyPacketProvider.cs +++ b/src/PacketLogger/Models/Packets/DummyPacketProvider.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Reactive.Linq; +using System.Threading; using System.Threading.Tasks; using DynamicData; using DynamicData.Binding; @@ -22,25 +23,34 @@ namespace PacketLogger.Models.Packets; /// public class DummyPacketProvider : IPacketProvider, IDisposable { + private long _index = 0; + /// /// Initializes a new instance of the class. /// public DummyPacketProvider() { - var index = 0; Packets = new SourceList(); - Packets.Add(new PacketInfo(index++, DateTime.Now, PacketSource.Client, "#cl")); - Packets.Add(new PacketInfo(index++, DateTime.Now, PacketSource.Client, "cl")); + Packets.Add(new PacketInfo(_index++, DateTime.Now, PacketSource.Client, "#cl")); + Packets.Add(new PacketInfo(_index++, DateTime.Now, PacketSource.Client, "cl")); for (var i = 0; i < 1000; i++) { Packets.Add - (new PacketInfo(index++, DateTime.Now.AddSeconds(-1000 + i), PacketSource.Client, "walk 10 10")); + (new PacketInfo(_index++, DateTime.Now.AddSeconds(-1000 + i), PacketSource.Client, "walk 10 10")); Packets.Add - (new PacketInfo(index++, DateTime.Now.AddSeconds(-1000 + i), PacketSource.Server, "mv 1 50 52 123 123 89012390812 189023 182309 1823 189023 901283 091823 091823 901823 901283 091283 019283901283 901283 901 2831290 812390128390128213908139012839012839012390128390128938120938 1290 3190 adsadf")); + ( + new PacketInfo + ( + _index++, + DateTime.Now.AddSeconds(-1000 + i), + PacketSource.Server, + "mv 1 50 52 123 123 89012390812 189023 182309 1823 189023 901283 091823 091823 901823 901283 091283 019283901283 901283 901 2831290 812390128390128213908139012839012839012390128390128938120938 1290 3190 adsadf" + ) + ); Packets.Add - (new PacketInfo(index++, DateTime.Now.AddSeconds(-1000 + i), PacketSource.Client, "walk 12 14")); + (new PacketInfo(_index++, DateTime.Now.AddSeconds(-1000 + i), PacketSource.Client, "walk 12 14")); Packets.Add - (new PacketInfo(index++, DateTime.Now.AddSeconds(-1000 + i), PacketSource.Server, "mv 1 48 43")); + (new PacketInfo(_index++, DateTime.Now.AddSeconds(-1000 + i), PacketSource.Server, "mv 1 48 43")); } } @@ -61,6 +71,9 @@ public class DummyPacketProvider : IPacketProvider, IDisposable set { } } + /// + public string Name => "Empty"; + /// public bool IsOpen => false; @@ -85,6 +98,20 @@ public class DummyPacketProvider : IPacketProvider, IDisposable Packets.Clear(); } + /// + public Task SendPacket(string packetString, CancellationToken ct = default) + { + Packets.Add(new PacketInfo(_index++, DateTime.Now, PacketSource.Client, packetString)); + return Task.FromResult(Result.FromSuccess()); + } + + /// + public Task ReceivePacket(string packetString, CancellationToken ct = default) + { + Packets.Add(new PacketInfo(_index++, DateTime.Now, PacketSource.Server, packetString)); + return Task.FromResult(Result.FromSuccess()); + } + /// public void Dispose() { diff --git a/src/PacketLogger/Models/Packets/FilePacketProvider.cs b/src/PacketLogger/Models/Packets/FilePacketProvider.cs index 9522afb..ed082e2 100644 --- a/src/PacketLogger/Models/Packets/FilePacketProvider.cs +++ b/src/PacketLogger/Models/Packets/FilePacketProvider.cs @@ -11,6 +11,7 @@ using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using DynamicData; using NosSmooth.PacketSerializer.Abstractions.Attributes; @@ -26,6 +27,7 @@ public class FilePacketProvider : IPacketProvider { private readonly string _fileName; private SourceList? _packets; + private long _index = 0; /// /// Initializes a new instance of the class. @@ -36,6 +38,9 @@ public class FilePacketProvider : IPacketProvider _fileName = fileName; } + /// + public string Name => Path.GetFileName(_fileName); + /// public bool IsOpen => false; @@ -66,7 +71,7 @@ public class FilePacketProvider : IPacketProvider } var packets = new SourceList(); - var index = 0; + _index = 0; foreach (var line in await File.ReadAllLinesAsync(_fileName)) { if (line.Length <= 1) @@ -81,7 +86,7 @@ public class FilePacketProvider : IPacketProvider ( new PacketInfo ( - index++, + _index++, DateTime.Now, splitted[0] == "[Recv]" ? PacketSource.Server : PacketSource.Client, splitted[1] @@ -94,7 +99,7 @@ public class FilePacketProvider : IPacketProvider ( new PacketInfo ( - index++, + _index++, DateTime.Parse(splitted[0].Trim('[', ']')), splitted[1] == "[Recv]" ? PacketSource.Server : PacketSource.Client, splitted[2] @@ -117,6 +122,20 @@ public class FilePacketProvider : IPacketProvider // Clearing packets from file does not make any sense... } + /// + public Task SendPacket(string packetString, CancellationToken ct = default) + { + Packets.Add(new PacketInfo(_index++, DateTime.Now, PacketSource.Client, packetString)); + return Task.FromResult(Result.FromSuccess()); + } + + /// + public Task ReceivePacket(string packetString, CancellationToken ct = default) + { + Packets.Add(new PacketInfo(_index++, DateTime.Now, PacketSource.Server, packetString)); + return Task.FromResult(Result.FromSuccess()); + } + /// public event PropertyChangedEventHandler? PropertyChanged; diff --git a/src/PacketLogger/Models/Packets/IPacketProvider.cs b/src/PacketLogger/Models/Packets/IPacketProvider.cs index edb88d8..0d09065 100644 --- a/src/PacketLogger/Models/Packets/IPacketProvider.cs +++ b/src/PacketLogger/Models/Packets/IPacketProvider.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Threading; using System.Threading.Tasks; using DynamicData; using PacketLogger.Models.Filters; @@ -20,6 +21,11 @@ namespace PacketLogger.Models.Packets; /// public interface IPacketProvider : INotifyPropertyChanged, IDisposable { + /// + /// Gets the name. + /// + public string Name { get; } + /// /// Gets whether was called and successfully finished. /// @@ -56,4 +62,20 @@ public interface IPacketProvider : INotifyPropertyChanged, IDisposable /// Clear all packets. /// public void Clear(); + + /// + /// Send the given packets. + /// + /// The packet to send. + /// The cancellation token used for cancelling the operation. + /// A result that may or may not have succeeded. + Task SendPacket(string packetString, CancellationToken ct = default); + + /// + /// Receive the given packet. + /// + /// The packet to send. + /// The cancellation token used for cancelling the operation. + /// A result that may or may not have succeeded. + Task ReceivePacket(string packetString, CancellationToken ct = default); } \ No newline at end of file diff --git a/src/PacketLogger/Models/Packets/IPacketSender.cs b/src/PacketLogger/Models/Packets/IPacketSender.cs deleted file mode 100644 index 4b040f0..0000000 --- a/src/PacketLogger/Models/Packets/IPacketSender.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// IPacketSender.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.Threading; -using System.Threading.Tasks; -using Avalonia.Controls; -using Remora.Results; - -namespace PacketLogger.Models.Packets; - -/// -/// A provider that may as well send or receive packets. -/// -public interface IPacketSender : IPacketProvider -{ - /// - /// Send the given packets. - /// - /// The packet to send. - /// The cancellation token used for cancelling the operation. - /// A result that may or may not have succeeded. - Task SendPacket(string packetString, CancellationToken ct = default); - - /// - /// Receive the given packet. - /// - /// The packet to send. - /// The cancellation token used for cancelling the operation. - /// A result that may or may not have succeeded. - Task ReceivePacket(string packetString, CancellationToken ct = default); -} \ No newline at end of file diff --git a/src/PacketLogger/ViewModels/DockFactory.cs b/src/PacketLogger/ViewModels/DockFactory.cs index 47bed52..1088c8b 100644 --- a/src/PacketLogger/ViewModels/DockFactory.cs +++ b/src/PacketLogger/ViewModels/DockFactory.cs @@ -6,7 +6,9 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Reactive; +using System.Reactive.Linq; using Dock.Avalonia.Controls; using Dock.Model.Controls; using Dock.Model.Core; @@ -15,6 +17,8 @@ using Dock.Model.Mvvm.Controls; using NosSmooth.Comms.Local; using NosSmooth.Core.Stateful; using PacketLogger.Models; +using PacketLogger.Models.Packets; +using PacketLogger.Views; using ReactiveUI; namespace PacketLogger.ViewModels; @@ -25,6 +29,7 @@ namespace PacketLogger.ViewModels; public class DockFactory : Factory, IDisposable { private readonly StatefulRepository _repository; + private readonly ObservableCollection _providers; private readonly NostaleProcesses _processes; private readonly CommsInjector _injector; @@ -34,20 +39,42 @@ public class DockFactory : Factory, IDisposable /// /// Initializes a new instance of the class. /// + /// The providers. /// The nostale processes. /// The communications injector. /// The repository. - public DockFactory(NostaleProcesses processes, CommsInjector injector, StatefulRepository repository) + public DockFactory + ( + ObservableCollection providers, + NostaleProcesses processes, + CommsInjector injector, + StatefulRepository repository + ) { + _providers = providers; _processes = processes; _repository = repository; _injector = injector; } - /// - public override IDockWindow CreateDockWindow() + /// + /// Document loaded event. + /// + public event Action? DocumentLoaded; + + /// + /// Document closed event. + /// + public event Action? DocumentClosed; + + private void OnDocumentLoaded(DocumentViewModel documentViewModel) + { + DocumentLoaded?.Invoke(documentViewModel); + } + + private void OnDocumentClosed(DocumentViewModel documentViewModel) { - return base.CreateDockWindow(); + DocumentClosed?.Invoke(documentViewModel); } /// @@ -60,14 +87,22 @@ public class DockFactory : Factory, IDisposable /// Creaate and load a document. /// /// The function to use to load the document. - public void CreateLoadedDocument(Func> load) + public void CreateLoadedDocument(Func> load) { if (_documentDock is null) { return; } - var document = new PacketLogDocumentViewModel(_injector, _repository, _processes) + var document = new DocumentViewModel + ( + _injector, + _repository, + _providers, + _processes, + OnDocumentLoaded, + OnDocumentClosed + ) { Id = $"New tab", Title = $"New tab" }; var observable = load(document); @@ -101,7 +136,15 @@ public class DockFactory : Factory, IDisposable } var index = documentDock.VisibleDockables?.Count + 1; - var document = new PacketLogDocumentViewModel(_injector, _repository, _processes) + var document = new DocumentViewModel + ( + _injector, + _repository, + _providers, + _processes, + OnDocumentLoaded, + OnDocumentClosed + ) { Id = $"New tab {index}", Title = $"New tab {index}" }; AddDockable(documentDock, document); @@ -115,7 +158,15 @@ public class DockFactory : Factory, IDisposable /// public override IRootDock CreateLayout() { - var initialTab = new PacketLogDocumentViewModel(_injector, _repository, _processes) + var initialTab = new DocumentViewModel + ( + _injector, + _repository, + _providers, + _processes, + OnDocumentLoaded, + OnDocumentClosed + ) { Id = $"New tab", Title = $"New tab" }; var documentDock = CreateDocumentDock(); documentDock.IsCollapsable = false; diff --git a/src/PacketLogger/ViewModels/PacketLogDocumentViewModel.cs b/src/PacketLogger/ViewModels/DocumentViewModel.cs similarity index 70% rename from src/PacketLogger/ViewModels/PacketLogDocumentViewModel.cs rename to src/PacketLogger/ViewModels/DocumentViewModel.cs index 4faf5de..8fe8905 100644 --- a/src/PacketLogger/ViewModels/PacketLogDocumentViewModel.cs +++ b/src/PacketLogger/ViewModels/DocumentViewModel.cs @@ -1,5 +1,5 @@ // -// PacketLogDocumentViewModel.cs +// DocumentViewModel.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. @@ -31,23 +31,39 @@ using ReactiveUI; namespace PacketLogger.ViewModels; /// -public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDisposable +public class DocumentViewModel : Document, INotifyPropertyChanged, IDisposable { private readonly CommsInjector _injector; + private readonly ObservableCollection _providers; private readonly NostaleProcesses _processes; + private readonly Action _onDocumentUnloaded; private CancellationTokenSource _ctSource; + private IPacketProvider? _packetProvider; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The injector. /// The repository. + /// The providers. /// The NosTale processes collection. - public PacketLogDocumentViewModel(CommsInjector injector, StatefulRepository repository, NostaleProcesses processes) + /// The action to call on loaded. + /// The action to call on document unloaded/closed. + public DocumentViewModel + ( + CommsInjector injector, + StatefulRepository repository, + ObservableCollection providers, + NostaleProcesses processes, + Action onDocumentLoaded, + Action onDocumentUnloaded + ) { _ctSource = new CancellationTokenSource(); _injector = injector; + _providers = providers; _processes = processes; + _onDocumentUnloaded = onDocumentUnloaded; OpenDummy = ReactiveCommand.CreateFromTask ( () => Task.Run @@ -56,8 +72,10 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis { Loading = true; Name = "Dummy"; - LogViewModel = new LogTabViewModel(new DummyPacketProvider()); + _packetProvider = new DummyPacketProvider(); + NestedViewModel = new PacketLogViewModel(_packetProvider); Loaded = true; + onDocumentLoaded(this); } ) ); @@ -82,6 +100,7 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis var path = result[0]; var provider = new FilePacketProvider(path); + _packetProvider = provider; var openResult = await provider.Open(); if (!openResult.IsSuccess) @@ -91,9 +110,10 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis } Title = Path.GetFileName(path); - LogViewModel = new LogTabViewModel(provider); + NestedViewModel = new PacketLogViewModel(provider); Loaded = true; Loading = false; + onDocumentLoaded(this); } ); @@ -111,6 +131,7 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis } var provider = new CommsPacketProvider(connection); + _packetProvider = provider; repository.SetEntity(connection.Client, provider); var contractResult = await connection.Connection.ContractHanshake @@ -137,12 +158,25 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis ) .Subscribe(); - LogViewModel = new LogTabViewModel(provider); + NestedViewModel = new PacketLogViewModel(provider); Title = handshakeResponse.CharacterName ?? $"Not in game ({process.Process.Id})"; Loading = false; Loaded = true; + onDocumentLoaded(this); } ); + + OpenSender = ReactiveCommand.Create + ( + provider => + { + Loading = true; + NestedViewModel = new PacketSenderViewModel(provider); + Title = $"Sender ({provider.Name})"; + Loaded = true; + Loading = false; + } + ); } /// @@ -150,6 +184,16 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis /// public ObservableCollection Processes => _processes.Processes; + /// + /// Gets packet provider. + /// + public IPacketProvider? Provider => _packetProvider; + + /// + /// Gets the open providers. + /// + public ObservableCollection Providers => _providers; + /// /// Gets or sets the name of the tab. /// @@ -168,7 +212,12 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis /// /// Gets the log tab view model. /// - public LogTabViewModel? LogViewModel { get; private set; } + public ViewModelBase? NestedViewModel { get; private set; } + + /// + /// Gets command for opening a dummy. + /// + public ReactiveCommand OpenSender { get; } /// /// Gets command for opening a dummy. @@ -188,7 +237,8 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis /// public override bool OnClose() { - LogViewModel?.Provider.Close().GetAwaiter().GetResult(); + _onDocumentUnloaded(this); + _packetProvider?.Close().GetAwaiter().GetResult(); Dispose(); return base.OnClose(); } @@ -198,9 +248,10 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis { _ctSource.Cancel(); _ctSource.Dispose(); - LogViewModel?.Dispose(); + (NestedViewModel as IDisposable)?.Dispose(); OpenDummy.Dispose(); OpenFile.Dispose(); OpenProcess.Dispose(); + OpenSender.Dispose(); } } \ No newline at end of file diff --git a/src/PacketLogger/ViewModels/MainWindowViewModel.cs b/src/PacketLogger/ViewModels/MainWindowViewModel.cs index 5161614..c2a6c1e 100644 --- a/src/PacketLogger/ViewModels/MainWindowViewModel.cs +++ b/src/PacketLogger/ViewModels/MainWindowViewModel.cs @@ -11,13 +11,12 @@ using System.ComponentModel; using System.IO; using System.Linq; using System.Reactive; -using System.Reactive.Linq; +using System.Reactive.Concurrency; using System.Reflection; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Dock.Model.Controls; -using Dock.Model.Core; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NosSmooth.Comms.Local.Extensions; @@ -27,7 +26,6 @@ using NosSmooth.PacketSerializer.Extensions; using NosSmooth.PacketSerializer.Packets; using PacketLogger.Models; using PacketLogger.Models.Packets; -using PacketLogger.Views; using ReactiveUI; namespace PacketLogger.ViewModels; @@ -47,6 +45,7 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged .AddLogging(b => b.ClearProviders().AddConsole()) .AddSingleton() .AddSingleton() + .AddSingleton>(_ => Providers) .AddNostaleCore() .AddStatefulInjector() .AddStatefulEntity() @@ -74,12 +73,28 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged } } + _factory.DocumentLoaded += doc => + { + if (doc.Provider is not null) + { + RxApp.MainThreadScheduler.Schedule(() => Providers.Add(doc.Provider)); + } + }; + + _factory.DocumentClosed += doc => + { + if (doc.Provider is not null) + { + RxApp.MainThreadScheduler.Schedule(() => Providers.Remove(doc.Provider)); + } + }; + SaveAll = ReactiveCommand.CreateFromTask ( async () => { - if (Layout?.FocusedDockable is PacketLogDocumentViewModel activeDocument && activeDocument.Loaded - && activeDocument.LogViewModel is not null) + if (Layout?.FocusedDockable is DocumentViewModel activeDocument && activeDocument.Loaded + && activeDocument.NestedViewModel is not null) { var mainWindow = (App.Current!.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime) ?.MainWindow; @@ -94,9 +109,15 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged return; } + if (activeDocument.Provider is null) + { + return; + } + using var file = File.OpenWrite(result); using var streamWriter = new StreamWriter(file); - foreach (var packet in activeDocument.LogViewModel.Provider.Packets.Items) + + foreach (var packet in activeDocument.Provider.Packets.Items) { await streamWriter.WriteLineAsync ( @@ -111,8 +132,8 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged ( async () => { - if (Layout?.FocusedDockable is PacketLogDocumentViewModel activeDocument && activeDocument.Loaded - && activeDocument.LogViewModel is not null) + if (Layout?.FocusedDockable is DocumentViewModel activeDocument && activeDocument.Loaded + && activeDocument.NestedViewModel is not null) { var mainWindow = (App.Current!.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime) ?.MainWindow; @@ -126,9 +147,14 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged return; } + if (activeDocument.NestedViewModel is not PacketLogViewModel packetLogVM) + { + return; + } + using var file = File.OpenWrite(result); using var streamWriter = new StreamWriter(file); - foreach (var packet in activeDocument.LogViewModel.FilteredPackets) + foreach (var packet in packetLogVM.FilteredPackets) { await streamWriter.WriteLineAsync ( @@ -148,6 +174,9 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged Connect = ReactiveCommand.Create (process => _factory.CreateLoadedDocument(doc => doc.OpenProcess.Execute((NostaleProcess)process[0]!))); + OpenSender = ReactiveCommand.Create + (provider => _factory.CreateLoadedDocument(doc => doc.OpenSender.Execute((IPacketProvider)provider[0]!))); + NewTab = ReactiveCommand.Create (() => _factory.DocumentDock.CreateDocument?.Execute(null)); @@ -160,6 +189,11 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged /// public ObservableCollection Processes => _processes.Processes; + /// + /// Gets the packet provider. + /// + public ObservableCollection Providers { get; } = new ObservableCollection(); + /// /// Gets or sets the layout. /// @@ -190,6 +224,11 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged /// public ReactiveCommand OpenEmpty { get; } + /// + /// Gets the command that opens empty logger. + /// + public ReactiveCommand OpenSender { get; } + /// /// Gets the command that opens empty logger. /// diff --git a/src/PacketLogger/ViewModels/LogFilterTabViewModel.cs b/src/PacketLogger/ViewModels/PacketLogFilterViewModel.cs similarity index 91% rename from src/PacketLogger/ViewModels/LogFilterTabViewModel.cs rename to src/PacketLogger/ViewModels/PacketLogFilterViewModel.cs index f7b6cf6..85147ce 100644 --- a/src/PacketLogger/ViewModels/LogFilterTabViewModel.cs +++ b/src/PacketLogger/ViewModels/PacketLogFilterViewModel.cs @@ -1,5 +1,5 @@ // -// LogFilterTabViewModel.cs +// PacketLogFilterViewModel.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. @@ -17,12 +17,12 @@ using ReactiveUI; namespace PacketLogger.ViewModels; /// -public class LogFilterTabViewModel : ViewModelBase, IDisposable +public class PacketLogFilterViewModel : ViewModelBase, IDisposable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public LogFilterTabViewModel() + public PacketLogFilterViewModel() { Filters = new ObservableCollection(); RemoveCurrent = ReactiveCommand.Create diff --git a/src/PacketLogger/ViewModels/LogTabViewModel.cs b/src/PacketLogger/ViewModels/PacketLogViewModel.cs similarity index 91% rename from src/PacketLogger/ViewModels/LogTabViewModel.cs rename to src/PacketLogger/ViewModels/PacketLogViewModel.cs index 3152707..26d329a 100644 --- a/src/PacketLogger/ViewModels/LogTabViewModel.cs +++ b/src/PacketLogger/ViewModels/PacketLogViewModel.cs @@ -1,5 +1,5 @@ // -// LogTabViewModel.cs +// PacketLogViewModel.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. @@ -26,7 +26,7 @@ using Reloaded.Memory.Kernel32; namespace PacketLogger.ViewModels; /// -public class LogTabViewModel : ViewModelBase, IDisposable +public class PacketLogViewModel : ViewModelBase, IDisposable { private readonly ReadOnlyObservableCollection _packets; private readonly IDisposable _cleanUp; @@ -34,10 +34,10 @@ public class LogTabViewModel : ViewModelBase, IDisposable private bool _logSent = true; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The packet provider. - public LogTabViewModel(IPacketProvider packetProvider) + public PacketLogViewModel(IPacketProvider packetProvider) { Provider = packetProvider; @@ -120,12 +120,12 @@ public class LogTabViewModel : ViewModelBase, IDisposable /// /// Gets the send filter model. /// - public LogFilterTabViewModel SendFilter { get; } + public PacketLogFilterViewModel SendFilter { get; } /// /// Gets the receive filter model. /// - public LogFilterTabViewModel RecvFilter { get; } + public PacketLogFilterViewModel RecvFilter { get; } /// /// Gets the currently applied filter. @@ -211,16 +211,16 @@ public class LogTabViewModel : ViewModelBase, IDisposable CurrentFilter = new SendRecvFilter(sendFilter, recvFilter); } - private IFilter CreateCompound(LogFilterTabViewModel logFilterTab) + private IFilter CreateCompound(PacketLogFilterViewModel packetLogFilter) { List filters = new List(); - foreach (var filter in logFilterTab.Filters) + foreach (var filter in packetLogFilter.Filters) { filters.Add(FilterCreator.BuildFilter(filter.Type, filter.Value)); } - return new CompoundFilter(!logFilterTab.Whitelist, filters.ToArray()); + return new CompoundFilter(!packetLogFilter.Whitelist, filters.ToArray()); } /// diff --git a/src/PacketLogger/ViewModels/PacketSendSubViewModel.cs b/src/PacketLogger/ViewModels/PacketSendSubViewModel.cs new file mode 100644 index 0000000..e23a7a0 --- /dev/null +++ b/src/PacketLogger/ViewModels/PacketSendSubViewModel.cs @@ -0,0 +1,130 @@ +// +// PacketSendSubViewModel.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.Reactive; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; +using NosSmooth.PacketSerializer.Abstractions.Attributes; +using PacketLogger.Models.Packets; +using ReactiveUI; + +namespace PacketLogger.ViewModels; + +/// +public class PacketSendSubViewModel : ViewModelBase, IDisposable +{ + private readonly IPacketProvider _sender; + private string[]? _cachedPacketData; + private IDisposable? _sendingTask; + private SemaphoreSlim _semaphore; + + /// + /// Initializes a new instance of the class. + /// + /// The packet source to use. + /// The sender to send packets to. + public PacketSendSubViewModel(PacketSource source, IPacketProvider sender) + { + _semaphore = new SemaphoreSlim(1, 1); + Source = source; + _sender = sender; + + SendPackets = ReactiveCommand.CreateFromTask(SendPacketData); + ToggleRepetetiveSend = ReactiveCommand.Create( + () => + { + if (IsSending) + { + _semaphore.Wait(); + _sendingTask?.Dispose(); + _sendingTask = null; + _semaphore.Release(); + } + else + { + _semaphore.Wait(); + _cachedPacketData = null; + _sendingTask?.Dispose(); + _sendingTask = Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(RepetitionDelay)) + .Subscribe + ( + _ => + { + SendPacketData().GetAwaiter().GetResult(); + } + ); + _semaphore.Release(); + } + + IsSending = !IsSending; + }); + } + + /// + /// Gets the source to send the packets as. + /// + public PacketSource Source { get; } + + /// + /// Gets or sets whether current repetetively sending. + /// + public bool IsSending { get; private set; } + + /// + /// Gets or sets the packets to send separated by a line. + /// + public string PacketsData { get; set; } = string.Empty; + + /// + /// The delay of repetition in milliseconds. + /// + public int RepetitionDelay { get; set; } = 100; + + /// + /// Gets or sets the command used to send the packets. + /// + public ReactiveCommand SendPackets { get; } + + /// + /// Gets the command used for toggling repetetive send. + /// + public ReactiveCommand ToggleRepetetiveSend { get; } + + /// + public void Dispose() + { + _sendingTask?.Dispose(); + SendPackets.Dispose(); + ToggleRepetetiveSend.Dispose(); + } + + private async Task SendPacketData() + { + if (!IsSending || _cachedPacketData is null) + { + _cachedPacketData = PacketsData.Split('\n', StringSplitOptions.RemoveEmptyEntries); + } + + foreach (var line in _cachedPacketData) + { + await Send(line); + } + } + + private Task Send(string packetString) + { + if (Source == PacketSource.Server) + { + return _sender.ReceivePacket(packetString); + } + else + { + return _sender.SendPacket(packetString); + } + } +} \ No newline at end of file diff --git a/src/PacketLogger/ViewModels/PacketSenderViewModel.cs b/src/PacketLogger/ViewModels/PacketSenderViewModel.cs new file mode 100644 index 0000000..fe08f21 --- /dev/null +++ b/src/PacketLogger/ViewModels/PacketSenderViewModel.cs @@ -0,0 +1,44 @@ +// +// PacketSenderViewModel.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.Reactive; +using NosSmooth.PacketSerializer.Abstractions.Attributes; +using PacketLogger.Models.Packets; +using ReactiveUI; + +namespace PacketLogger.ViewModels; + +/// +public class PacketSenderViewModel : ViewModelBase, IDisposable +{ + /// + /// Initializes a new instance of the class. + /// + /// The packet sender. + public PacketSenderViewModel(IPacketProvider packetSender) + { + RecvSubViewModel = new PacketSendSubViewModel(PacketSource.Server, packetSender); + SendSubViewModel = new PacketSendSubViewModel(PacketSource.Client, packetSender); + } + + /// + /// Gets the packet recv sub view. + /// + public PacketSendSubViewModel RecvSubViewModel { get; } + + /// + /// Gets the packet send sub view. + /// + public PacketSendSubViewModel SendSubViewModel { get; } + + /// + public void Dispose() + { + RecvSubViewModel.Dispose(); + SendSubViewModel.Dispose(); + } +} \ No newline at end of file diff --git a/src/PacketLogger/Views/PacketLogDocumentView.axaml b/src/PacketLogger/Views/DocumentView.axaml similarity index 78% rename from src/PacketLogger/Views/PacketLogDocumentView.axaml rename to src/PacketLogger/Views/DocumentView.axaml index 81ab788..7bbf521 100644 --- a/src/PacketLogger/Views/PacketLogDocumentView.axaml +++ b/src/PacketLogger/Views/DocumentView.axaml @@ -6,9 +6,9 @@ xmlns:viewModels="clr-namespace:PacketLogger.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:i="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" - x:Class="PacketLogger.Views.PacketLogDocumentView"> + x:Class="PacketLogger.Views.DocumentView"> - + @@ -82,11 +82,28 @@ - + + + + + + + + +