M src/PacketLogger/Models/Packets/CommsPacketProvider.cs => src/PacketLogger/Models/Packets/CommsPacketProvider.cs +4 -1
@@ 20,7 20,7 @@ namespace PacketLogger.Models.Packets;
/// <summary>
/// A packet provider using a connection to a nostale client.
/// </summary>
-public class CommsPacketProvider : IPacketSender
+public class CommsPacketProvider : IPacketProvider
{
private readonly Comms _comms;
private long _currentIndex;
@@ 39,6 39,9 @@ public class CommsPacketProvider : IPacketSender
public event PropertyChangedEventHandler? PropertyChanged;
/// <inheritdoc />
+ public string Name => "TODO";
+
+ /// <inheritdoc />
public bool IsOpen => _comms.Connection.Connection.State == ConnectionState.Open;
/// <inheritdoc />
M src/PacketLogger/Models/Packets/DummyPacketProvider.cs => src/PacketLogger/Models/Packets/DummyPacketProvider.cs +34 -7
@@ 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;
/// <inheritdoc />
public class DummyPacketProvider : IPacketProvider, IDisposable
{
+ private long _index = 0;
+
/// <summary>
/// Initializes a new instance of the <see cref="DummyPacketProvider"/> class.
/// </summary>
public DummyPacketProvider()
{
- var index = 0;
Packets = new SourceList<PacketInfo>();
- 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"));
}
}
@@ 62,6 72,9 @@ public class DummyPacketProvider : IPacketProvider, IDisposable
}
/// <inheritdoc />
+ public string Name => "Empty";
+
+ /// <inheritdoc />
public bool IsOpen => false;
/// <inheritdoc />
@@ 86,6 99,20 @@ public class DummyPacketProvider : IPacketProvider, IDisposable
}
/// <inheritdoc />
+ public Task<Result> SendPacket(string packetString, CancellationToken ct = default)
+ {
+ Packets.Add(new PacketInfo(_index++, DateTime.Now, PacketSource.Client, packetString));
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ /// <inheritdoc />
+ public Task<Result> ReceivePacket(string packetString, CancellationToken ct = default)
+ {
+ Packets.Add(new PacketInfo(_index++, DateTime.Now, PacketSource.Server, packetString));
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ /// <inheritdoc />
public void Dispose()
{
Packets.Dispose();
M src/PacketLogger/Models/Packets/FilePacketProvider.cs => src/PacketLogger/Models/Packets/FilePacketProvider.cs +22 -3
@@ 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<PacketInfo>? _packets;
+ private long _index = 0;
/// <summary>
/// Initializes a new instance of the <see cref="FilePacketProvider"/> class.
@@ 37,6 39,9 @@ public class FilePacketProvider : IPacketProvider
}
/// <inheritdoc />
+ public string Name => Path.GetFileName(_fileName);
+
+ /// <inheritdoc />
public bool IsOpen => false;
/// <inheritdoc />
@@ 66,7 71,7 @@ public class FilePacketProvider : IPacketProvider
}
var packets = new SourceList<PacketInfo>();
- 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]
@@ 118,6 123,20 @@ public class FilePacketProvider : IPacketProvider
}
/// <inheritdoc />
+ public Task<Result> SendPacket(string packetString, CancellationToken ct = default)
+ {
+ Packets.Add(new PacketInfo(_index++, DateTime.Now, PacketSource.Client, packetString));
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ /// <inheritdoc />
+ public Task<Result> ReceivePacket(string packetString, CancellationToken ct = default)
+ {
+ Packets.Add(new PacketInfo(_index++, DateTime.Now, PacketSource.Server, packetString));
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ /// <inheritdoc />
public event PropertyChangedEventHandler? PropertyChanged;
/// <inheritdoc/>
M src/PacketLogger/Models/Packets/IPacketProvider.cs => src/PacketLogger/Models/Packets/IPacketProvider.cs +22 -0
@@ 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;
@@ 21,6 22,11 @@ namespace PacketLogger.Models.Packets;
public interface IPacketProvider : INotifyPropertyChanged, IDisposable
{
/// <summary>
+ /// Gets the name.
+ /// </summary>
+ public string Name { get; }
+
+ /// <summary>
/// Gets whether <see cref="Open"/> was called and successfully finished.
/// </summary>
public bool IsOpen { get; }
@@ 56,4 62,20 @@ public interface IPacketProvider : INotifyPropertyChanged, IDisposable
/// Clear all packets.
/// </summary>
public void Clear();
+
+ /// <summary>
+ /// Send the given packets.
+ /// </summary>
+ /// <param name="packetString">The packet to send.</param>
+ /// <param name="ct">The cancellation token used for cancelling the operation.</param>
+ /// <returns>A result that may or may not have succeeded.</returns>
+ Task<Result> SendPacket(string packetString, CancellationToken ct = default);
+
+ /// <summary>
+ /// Receive the given packet.
+ /// </summary>
+ /// <param name="packetString">The packet to send.</param>
+ /// <param name="ct">The cancellation token used for cancelling the operation.</param>
+ /// <returns>A result that may or may not have succeeded.</returns>
+ Task<Result> ReceivePacket(string packetString, CancellationToken ct = default);
}=
\ No newline at end of file
D src/PacketLogger/Models/Packets/IPacketSender.cs => src/PacketLogger/Models/Packets/IPacketSender.cs +0 -34
@@ 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;
-
-/// <summary>
-/// A provider that may as well send or receive packets.
-/// </summary>
-public interface IPacketSender : IPacketProvider
-{
- /// <summary>
- /// Send the given packets.
- /// </summary>
- /// <param name="packetString">The packet to send.</param>
- /// <param name="ct">The cancellation token used for cancelling the operation.</param>
- /// <returns>A result that may or may not have succeeded.</returns>
- Task<Result> SendPacket(string packetString, CancellationToken ct = default);
-
- /// <summary>
- /// Receive the given packet.
- /// </summary>
- /// <param name="packetString">The packet to send.</param>
- /// <param name="ct">The cancellation token used for cancelling the operation.</param>
- /// <returns>A result that may or may not have succeeded.</returns>
- Task<Result> ReceivePacket(string packetString, CancellationToken ct = default);
-}>
\ No newline at end of file
M src/PacketLogger/ViewModels/DockFactory.cs => src/PacketLogger/ViewModels/DockFactory.cs +59 -8
@@ 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<IPacketProvider> _providers;
private readonly NostaleProcesses _processes;
private readonly CommsInjector _injector;
@@ 34,20 39,42 @@ public class DockFactory : Factory, IDisposable
/// <summary>
/// Initializes a new instance of the <see cref="DockFactory"/> class.
/// </summary>
+ /// <param name="providers">The providers.</param>
/// <param name="processes">The nostale processes.</param>
/// <param name="injector">The communications injector.</param>
/// <param name="repository">The repository.</param>
- public DockFactory(NostaleProcesses processes, CommsInjector injector, StatefulRepository repository)
+ public DockFactory
+ (
+ ObservableCollection<IPacketProvider> providers,
+ NostaleProcesses processes,
+ CommsInjector injector,
+ StatefulRepository repository
+ )
{
+ _providers = providers;
_processes = processes;
_repository = repository;
_injector = injector;
}
- /// <inheritdoc />
- public override IDockWindow CreateDockWindow()
+ /// <summary>
+ /// Document loaded event.
+ /// </summary>
+ public event Action<DocumentViewModel>? DocumentLoaded;
+
+ /// <summary>
+ /// Document closed event.
+ /// </summary>
+ public event Action<DocumentViewModel>? DocumentClosed;
+
+ private void OnDocumentLoaded(DocumentViewModel documentViewModel)
+ {
+ DocumentLoaded?.Invoke(documentViewModel);
+ }
+
+ private void OnDocumentClosed(DocumentViewModel documentViewModel)
{
- return base.CreateDockWindow();
+ DocumentClosed?.Invoke(documentViewModel);
}
/// <summary>
@@ 60,14 87,22 @@ public class DockFactory : Factory, IDisposable
/// Creaate and load a document.
/// </summary>
/// <param name="load">The function to use to load the document.</param>
- public void CreateLoadedDocument(Func<PacketLogDocumentViewModel, IObservable<Unit>> load)
+ public void CreateLoadedDocument(Func<DocumentViewModel, IObservable<Unit>> 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
/// <inheritdoc />
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;
R src/PacketLogger/ViewModels/PacketLogDocumentViewModel.cs => src/PacketLogger/ViewModels/DocumentViewModel.cs +61 -10
@@ 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;
/// <inheritdoc />
-public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDisposable
+public class DocumentViewModel : Document, INotifyPropertyChanged, IDisposable
{
private readonly CommsInjector _injector;
+ private readonly ObservableCollection<IPacketProvider> _providers;
private readonly NostaleProcesses _processes;
+ private readonly Action<DocumentViewModel> _onDocumentUnloaded;
private CancellationTokenSource _ctSource;
+ private IPacketProvider? _packetProvider;
/// <summary>
- /// Initializes a new instance of the <see cref="PacketLogDocumentViewModel"/> class.
+ /// Initializes a new instance of the <see cref="DocumentViewModel"/> class.
/// </summary>
/// <param name="injector">The injector.</param>
/// <param name="repository">The repository.</param>
+ /// <param name="providers">The providers.</param>
/// <param name="processes">The NosTale processes collection.</param>
- public PacketLogDocumentViewModel(CommsInjector injector, StatefulRepository repository, NostaleProcesses processes)
+ /// <param name="onDocumentLoaded">The action to call on loaded.</param>
+ /// <param name="onDocumentUnloaded">The action to call on document unloaded/closed.</param>
+ public DocumentViewModel
+ (
+ CommsInjector injector,
+ StatefulRepository repository,
+ ObservableCollection<IPacketProvider> providers,
+ NostaleProcesses processes,
+ Action<DocumentViewModel> onDocumentLoaded,
+ Action<DocumentViewModel> 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<CommsPacketProvider>(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<IPacketProvider>
+ (
+ provider =>
+ {
+ Loading = true;
+ NestedViewModel = new PacketSenderViewModel(provider);
+ Title = $"Sender ({provider.Name})";
+ Loaded = true;
+ Loading = false;
+ }
+ );
}
/// <summary>
@@ 151,6 185,16 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis
public ObservableCollection<NostaleProcess> Processes => _processes.Processes;
/// <summary>
+ /// Gets packet provider.
+ /// </summary>
+ public IPacketProvider? Provider => _packetProvider;
+
+ /// <summary>
+ /// Gets the open providers.
+ /// </summary>
+ public ObservableCollection<IPacketProvider> Providers => _providers;
+
+ /// <summary>
/// Gets or sets the name of the tab.
/// </summary>
public string Name { get; set; } = "New tab";
@@ 168,7 212,12 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis
/// <summary>
/// Gets the log tab view model.
/// </summary>
- public LogTabViewModel? LogViewModel { get; private set; }
+ public ViewModelBase? NestedViewModel { get; private set; }
+
+ /// <summary>
+ /// Gets command for opening a dummy.
+ /// </summary>
+ public ReactiveCommand<IPacketProvider, Unit> OpenSender { get; }
/// <summary>
/// Gets command for opening a dummy.
@@ 188,7 237,8 @@ public class PacketLogDocumentViewModel : Document, INotifyPropertyChanged, IDis
/// <inheritdoc />
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
M src/PacketLogger/ViewModels/MainWindowViewModel.cs => src/PacketLogger/ViewModels/MainWindowViewModel.cs +48 -9
@@ 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<DockFactory>()
.AddSingleton<NostaleProcesses>()
+ .AddSingleton<ObservableCollection<IPacketProvider>>(_ => Providers)
.AddNostaleCore()
.AddStatefulInjector()
.AddStatefulEntity<CommsPacketProvider>()
@@ 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<IList>
(process => _factory.CreateLoadedDocument(doc => doc.OpenProcess.Execute((NostaleProcess)process[0]!)));
+ OpenSender = ReactiveCommand.Create<IList>
+ (provider => _factory.CreateLoadedDocument(doc => doc.OpenSender.Execute((IPacketProvider)provider[0]!)));
+
NewTab = ReactiveCommand.Create
(() => _factory.DocumentDock.CreateDocument?.Execute(null));
@@ 161,6 190,11 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged
public ObservableCollection<NostaleProcess> Processes => _processes.Processes;
/// <summary>
+ /// Gets the packet provider.
+ /// </summary>
+ public ObservableCollection<IPacketProvider> Providers { get; } = new ObservableCollection<IPacketProvider>();
+
+ /// <summary>
/// Gets or sets the layout.
/// </summary>
public IRootDock? Layout { get; set; }
@@ 193,6 227,11 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged
/// <summary>
/// Gets the command that opens empty logger.
/// </summary>
+ public ReactiveCommand<IList, Unit> OpenSender { get; }
+
+ /// <summary>
+ /// Gets the command that opens empty logger.
+ /// </summary>
public ReactiveCommand<IList, Unit> Connect { get; }
/// <summary>
R src/PacketLogger/ViewModels/LogFilterTabViewModel.cs => src/PacketLogger/ViewModels/PacketLogFilterViewModel.cs +4 -4
@@ 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;
/// <inheritdoc />
-public class LogFilterTabViewModel : ViewModelBase, IDisposable
+public class PacketLogFilterViewModel : ViewModelBase, IDisposable
{
/// <summary>
- /// Initializes a new instance of the <see cref="LogFilterTabViewModel"/> class.
+ /// Initializes a new instance of the <see cref="PacketLogFilterViewModel"/> class.
/// </summary>
- public LogFilterTabViewModel()
+ public PacketLogFilterViewModel()
{
Filters = new ObservableCollection<FilterCreator.FilterData>();
RemoveCurrent = ReactiveCommand.Create
R src/PacketLogger/ViewModels/LogTabViewModel.cs => src/PacketLogger/ViewModels/PacketLogViewModel.cs +9 -9
@@ 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;
/// <inheritdoc />
-public class LogTabViewModel : ViewModelBase, IDisposable
+public class PacketLogViewModel : ViewModelBase, IDisposable
{
private readonly ReadOnlyObservableCollection<PacketInfo> _packets;
private readonly IDisposable _cleanUp;
@@ 34,10 34,10 @@ public class LogTabViewModel : ViewModelBase, IDisposable
private bool _logSent = true;
/// <summary>
- /// Initializes a new instance of the <see cref="LogTabViewModel"/> class.
+ /// Initializes a new instance of the <see cref="PacketLogViewModel"/> class.
/// </summary>
/// <param name="packetProvider">The packet provider.</param>
- public LogTabViewModel(IPacketProvider packetProvider)
+ public PacketLogViewModel(IPacketProvider packetProvider)
{
Provider = packetProvider;
@@ 120,12 120,12 @@ public class LogTabViewModel : ViewModelBase, IDisposable
/// <summary>
/// Gets the send filter model.
/// </summary>
- public LogFilterTabViewModel SendFilter { get; }
+ public PacketLogFilterViewModel SendFilter { get; }
/// <summary>
/// Gets the receive filter model.
/// </summary>
- public LogFilterTabViewModel RecvFilter { get; }
+ public PacketLogFilterViewModel RecvFilter { get; }
/// <summary>
/// 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<IFilter> filters = new List<IFilter>();
- 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());
}
/// <inheritdoc />
A src/PacketLogger/ViewModels/PacketSendSubViewModel.cs => src/PacketLogger/ViewModels/PacketSendSubViewModel.cs +130 -0
@@ 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;
+
+/// <inheritdoc />
+public class PacketSendSubViewModel : ViewModelBase, IDisposable
+{
+ private readonly IPacketProvider _sender;
+ private string[]? _cachedPacketData;
+ private IDisposable? _sendingTask;
+ private SemaphoreSlim _semaphore;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PacketSendSubViewModel"/> class.
+ /// </summary>
+ /// <param name="source">The packet source to use.</param>
+ /// <param name="sender">The sender to send packets to.</param>
+ 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;
+ });
+ }
+
+ /// <summary>
+ /// Gets the source to send the packets as.
+ /// </summary>
+ public PacketSource Source { get; }
+
+ /// <summary>
+ /// Gets or sets whether current repetetively sending.
+ /// </summary>
+ public bool IsSending { get; private set; }
+
+ /// <summary>
+ /// Gets or sets the packets to send separated by a line.
+ /// </summary>
+ public string PacketsData { get; set; } = string.Empty;
+
+ /// <summary>
+ /// The delay of repetition in milliseconds.
+ /// </summary>
+ public int RepetitionDelay { get; set; } = 100;
+
+ /// <summary>
+ /// Gets or sets the command used to send the packets.
+ /// </summary>
+ public ReactiveCommand<Unit, Unit> SendPackets { get; }
+
+ /// <summary>
+ /// Gets the command used for toggling repetetive send.
+ /// </summary>
+ public ReactiveCommand<Unit, Unit> ToggleRepetetiveSend { get; }
+
+ /// <inheritdoc />
+ 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
A src/PacketLogger/ViewModels/PacketSenderViewModel.cs => src/PacketLogger/ViewModels/PacketSenderViewModel.cs +44 -0
@@ 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;
+
+/// <inheritdoc />
+public class PacketSenderViewModel : ViewModelBase, IDisposable
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PacketSenderViewModel"/> class.
+ /// </summary>
+ /// <param name="packetSender">The packet sender.</param>
+ public PacketSenderViewModel(IPacketProvider packetSender)
+ {
+ RecvSubViewModel = new PacketSendSubViewModel(PacketSource.Server, packetSender);
+ SendSubViewModel = new PacketSendSubViewModel(PacketSource.Client, packetSender);
+ }
+
+ /// <summary>
+ /// Gets the packet recv sub view.
+ /// </summary>
+ public PacketSendSubViewModel RecvSubViewModel { get; }
+
+ /// <summary>
+ /// Gets the packet send sub view.
+ /// </summary>
+ public PacketSendSubViewModel SendSubViewModel { get; }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ RecvSubViewModel.Dispose();
+ SendSubViewModel.Dispose();
+ }
+}<
\ No newline at end of file
R src/PacketLogger/Views/PacketLogDocumentView.axaml => src/PacketLogger/Views/DocumentView.axaml +21 -4
@@ 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">
<Design.DataContext>
- <viewModels:PacketLogDocumentViewModel />
+ <viewModels:DocumentViewModel />
</Design.DataContext>
<Grid>
@@ 82,11 82,28 @@
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="34" Margin="-10,0,0,0" Text="Packet Sender" />
- <TextBlock Grid.Row="1" Grid.Column="1" Text="To be implemented..." />
+ <StackPanel Grid.Row="1" Grid.Column="1" Grid.RowSpan="3" Orientation="Vertical">
+ <TextBlock FontSize="30" Margin="0,0,0,5" Text="Open a sender for" />
+ <DataGrid Margin="0,0,30,0" Items="{Binding Providers}">
+ <DataGrid.Columns>
+ <DataGridTextColumn Header="Tab" Binding="{Binding Name}" />
+ <DataGridTemplateColumn Header="Open">
+ <DataGridTemplateColumn.CellTemplate>
+ <DataTemplate>
+ <Button Content="Open"
+ Command="{Binding $parent[UserControl].DataContext.OpenSender}"
+ IsEnabled="{Binding !$parent[UserControl].DataContext.Loading}"
+ CommandParameter="{Binding }" />
+ </DataTemplate>
+ </DataGridTemplateColumn.CellTemplate>
+ </DataGridTemplateColumn>
+ </DataGrid.Columns>
+ </DataGrid>
+ </StackPanel>
</Grid>
</Grid>
</Border>
- <ContentControl IsVisible="{Binding Loaded}" Content="{Binding LogViewModel}" />
+ <ContentControl IsVisible="{Binding Loaded}" Content="{Binding NestedViewModel}" />
</Grid>
</UserControl>=
\ No newline at end of file
R src/PacketLogger/Views/PacketLogDocumentView.axaml.cs => src/PacketLogger/Views/DocumentView.axaml.cs +4 -4
@@ 1,5 1,5 @@
//
-// PacketLogDocumentView.axaml.cs
+// DocumentView.axaml.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.
@@ 12,12 12,12 @@ using PropertyChanged;
namespace PacketLogger.Views;
[DoNotNotify]
-public partial class PacketLogDocumentView : UserControl
+public partial class DocumentView : UserControl
{
/// <summary>
- /// Initializes a new instance of the <see cref="PacketLogDocumentView"/> class.
+ /// Initializes a new instance of the <see cref="DocumentView"/> class.
/// </summary>
- public PacketLogDocumentView()
+ public DocumentView()
{
InitializeComponent();
}
M src/PacketLogger/Views/MainWindow.axaml => src/PacketLogger/Views/MainWindow.axaml +9 -1
@@ 40,7 40,15 @@
<MenuItem Header="Exit" Command="{Binding QuitApplication}" />
</MenuItem>
<MenuItem Header="_Tools">
- <MenuItem Header="_Packet Sender" />
+ <MenuItem Header="_Packet Sender" Command="{Binding Connect}" Items="{Binding Providers}">
+ <MenuItem.Styles>
+ <Style Selector="MenuItem">
+ <Setter Property="Header" Value="{Binding Name}" />
+ <Setter Property="Command" Value="{Binding OpenSender}" />
+ <Setter Property="CommandParameter" Value="{Binding SelectedItems, RelativeSource={RelativeSource Self}}" />
+ </Style>
+ </MenuItem.Styles>
+ </MenuItem>
<MenuItem Header="_Packet Analyzer" />
</MenuItem>
</Menu>
R src/PacketLogger/Views/LogFilterTabView.axaml => src/PacketLogger/Views/PacketLogFilterView.axaml +2 -2
@@ 4,9 4,9 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:PacketLogger.ViewModels"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="450"
- x:Class="PacketLogger.Views.LogFilterTabView">
+ x:Class="PacketLogger.Views.PacketLogFilterView">
<Design.DataContext>
- <vm:LogFilterTabViewModel />
+ <vm:PacketLogFilterViewModel />
</Design.DataContext>
<Grid ColumnDefinitions="*,*" RowDefinitions="*, 40, 40, 40">
R src/PacketLogger/Views/LogFilterTabView.axaml.cs => src/PacketLogger/Views/PacketLogFilterView.axaml.cs +4 -4
@@ 1,5 1,5 @@
//
-// LogFilterTabView.axaml.cs
+// PacketLogFilterView.axaml.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.
@@ 14,12 14,12 @@ using PropertyChanged;
namespace PacketLogger.Views;
[DoNotNotify]
-public partial class LogFilterTabView : UserControl
+public partial class PacketLogFilterView : UserControl
{
/// <summary>
- /// Initializes a new instance of the <see cref="LogFilterTabView"/> class.
+ /// Initializes a new instance of the <see cref="PacketLogFilterView"/> class.
/// </summary>
- public LogFilterTabView()
+ public PacketLogFilterView()
{
InitializeComponent();
this.FindControl<ComboBox>("FilterType").Items = Enum.GetValues<FilterCreator.FilterType>();
R src/PacketLogger/Views/LogTabView.axaml => src/PacketLogger/Views/PacketLogView.axaml +2 -2
@@ 4,7 4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:PacketLogger.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:Class="PacketLogger.Views.LogTabView"
+ x:Class="PacketLogger.Views.PacketLogView"
xmlns:i="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
xmlns:converters="clr-namespace:PacketLogger.Converters"
xmlns:views="clr-namespace:PacketLogger.Views"
@@ 15,7 15,7 @@
<converters:PacketSourceConverter x:Key="packetSourceConverter" />
</UserControl.Resources>
<Design.DataContext>
- <vm:LogTabViewModel />
+ <vm:PacketLogViewModel />
</Design.DataContext>
<SplitView IsPaneOpen="{Binding PaneOpen, Mode = TwoWay}" DisplayMode="CompactInline" PanePlacement="Right">
<SplitView.Pane>
R src/PacketLogger/Views/LogTabView.axaml.cs => src/PacketLogger/Views/PacketLogView.axaml.cs +4 -4
@@ 1,5 1,5 @@
//
-// LogTabView.axaml.cs
+// PacketLogView.axaml.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.
@@ 13,12 13,12 @@ using PropertyChanged;
namespace PacketLogger.Views;
[DoNotNotify]
-public partial class LogTabView : UserControl
+public partial class PacketLogView : UserControl
{
/// <summary>
- /// Initializes a new instance of the <see cref="LogTabView"/> class.
+ /// Initializes a new instance of the <see cref="PacketLogView"/> class.
/// </summary>
- public LogTabView()
+ public PacketLogView()
{
InitializeComponent();
}
A src/PacketLogger/Views/PacketSendSubView.axaml => src/PacketLogger/Views/PacketSendSubView.axaml +30 -0
@@ 0,0 1,30 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:viewModels="clr-namespace:PacketLogger.ViewModels"
+ xmlns:converters="clr-namespace:PacketLogger.Converters"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="PacketLogger.Views.PacketSendSubView">
+ <UserControl.Resources>
+ <converters:PacketSourceConverter x:Key="packetSourceConverter" />
+ </UserControl.Resources>
+ <Design.DataContext>
+ <viewModels:PacketSendSubViewModel />
+ </Design.DataContext>
+
+ <DockPanel>
+ <StackPanel DockPanel.Dock="Right" Width="150">
+ <Button Content="{Binding Source, Converter= {StaticResource packetSourceConverter}}"
+ Command="{Binding SendPackets}" />
+ <TextBlock Text="Repeat delay" />
+ <StackPanel>
+ <NumericUpDown Value="{Binding RepetitionDelay}" />
+ <TextBlock Text="ms" />
+ </StackPanel>
+ <Button Content="Start/Stop" Command="{Binding ToggleRepetetiveSend}" />
+ </StackPanel>
+
+ <TextBox Text="{Binding PacketsData}" AcceptsReturn="True" TextWrapping="NoWrap" Margin="0, 0, 20, 0" />
+ </DockPanel>
+</UserControl><
\ No newline at end of file
A src/PacketLogger/Views/PacketSendSubView.axaml.cs => src/PacketLogger/Views/PacketSendSubView.axaml.cs +29 -0
@@ 0,0 1,29 @@
+//
+// PacketSendSubView.axaml.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 Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using PropertyChanged;
+
+namespace PacketLogger.Views;
+
+[DoNotNotify]
+public partial class PacketSendSubView : UserControl
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PacketSendSubView"/> class.
+ /// </summary>
+ public PacketSendSubView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}<
\ No newline at end of file
A src/PacketLogger/Views/PacketSenderView.axaml => src/PacketLogger/Views/PacketSenderView.axaml +17 -0
@@ 0,0 1,17 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:viewModels="clr-namespace:PacketLogger.ViewModels"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="PacketLogger.Views.PacketSenderView">
+ <Design.DataContext>
+ <viewModels:PacketSenderViewModel />
+ </Design.DataContext>
+ <StackPanel Orientation="Vertical" Margin="10">
+ <TextBlock Text="Recv" Margin="0,0,0,5" />
+ <ContentControl Content="{Binding RecvSubViewModel}" />
+ <TextBlock Text="Send" Margin="0,5,0,5" />
+ <ContentControl Content="{Binding SendSubViewModel}" />
+ </StackPanel>
+</UserControl><
\ No newline at end of file
A src/PacketLogger/Views/PacketSenderView.axaml.cs => src/PacketLogger/Views/PacketSenderView.axaml.cs +29 -0
@@ 0,0 1,29 @@
+//
+// PacketSenderView.axaml.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 Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using PropertyChanged;
+
+namespace PacketLogger.Views;
+
+[DoNotNotify]
+public partial class PacketSenderView : UserControl
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PacketSenderView"/> class.
+ /// </summary>
+ public PacketSenderView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}<
\ No newline at end of file