diff --git a/PettingZoo.Benchmark/PettingZoo.Benchmark.csproj b/PettingZoo.Benchmark/PettingZoo.Benchmark.csproj new file mode 100644 index 0000000..bd81288 --- /dev/null +++ b/PettingZoo.Benchmark/PettingZoo.Benchmark.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + diff --git a/PettingZoo.Benchmark/Program.cs b/PettingZoo.Benchmark/Program.cs new file mode 100644 index 0000000..7d27337 --- /dev/null +++ b/PettingZoo.Benchmark/Program.cs @@ -0,0 +1,138 @@ +using System.Text; +using System.Text.Json; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PettingZoo.Benchmark +{ + /* + + + Small JSON + + + | Method | Mean | Error | StdDev | Gen 0 | Allocated | + |----------------- |----------:|----------:|----------:|-------:|----------:| + | TestJsonConvert | 13.226 us | 0.1808 us | 0.1603 us | 3.6316 | 15 KB | + | TestJTokenParse | 12.360 us | 0.2453 us | 0.5010 us | 3.6011 | 15 KB | + | TestReaderWriter | 6.398 us | 0.1260 us | 0.1294 us | 2.0294 | 8 KB | + | TestJsonDocument | 4.400 us | 0.0758 us | 0.0902 us | 2.1019 | 9 KB | + + + + Large JSON (25 MB) + + | Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | + |----------------- |-----------:|---------:|---------:|-----------:|-----------:|----------:|----------:| + | TestJsonConvert | 1,331.6 ms | 20.36 ms | 19.05 ms | 57000.0000 | 21000.0000 | 3000.0000 | 390 MB | + | TestJTokenParse | 1,411.0 ms | 27.28 ms | 24.18 ms | 62000.0000 | 23000.0000 | 4000.0000 | 415 MB | + | TestReaderWriter | 298.6 ms | 5.89 ms | 9.34 ms | 25000.0000 | 8000.0000 | 2000.0000 | 199 MB | + | TestJsonDocument | 278.5 ms | 5.29 ms | 6.30 ms | - | - | - | 246 MB | + + + */ + [MemoryDiagnoser] + public class JsonPrettyPrint + { + // Small Json file, which is likely to be typical for most RabbitMQ messages. + private const string Json = "{\"glossary\":{\"title\":\"example glossary\",\"GlossDiv\":{\"title\":\"S\",\"GlossList\":{\"GlossEntry\":{\"ID\":\"SGML\",\"SortAs\":\"SGML\",\"GlossTerm\":\"Standard Generalized Markup Language\",\"Acronym\":\"SGML\",\"Abbrev\":\"ISO 8879:1986\",\"GlossDef\":{\"para\":\"A meta-markup language, used to create markup languages such as DocBook.\",\"GlossSeeAlso\":[\"GML\",\"XML\"]},\"GlossSee\":\"markup\"}}}}}"; + + // To test with a large file instead, specify the file name here. + // For example, I've benchmarked it with this 25 MB JSON file: https://raw.githubusercontent.com/json-iterator/test-data/master/large-file.json + //private const string JsonFilename = ""; + private const string JsonFilename = "D:\\Temp\\large-file.json"; + + + private readonly string testJson; + + + public JsonPrettyPrint() + { + testJson = string.IsNullOrEmpty(JsonFilename) + ? Json + : File.ReadAllText(JsonFilename); + } + + + [Benchmark] + public string TestJsonConvert() + { + var obj = JsonConvert.DeserializeObject(testJson); + return JsonConvert.SerializeObject(obj, Formatting.Indented); + } + + + [Benchmark] + public string TestJTokenParse() + { + var obj = JToken.Parse(testJson); + return obj.ToString(Formatting.Indented); + } + + + [Benchmark] + public string TestReaderWriter() + { + using var stringReader = new StringReader(testJson); + using var jsonTextReader = new JsonTextReader(stringReader); + using var stringWriter = new StringWriter(); + using var jsonWriter = new JsonTextWriter(stringWriter); + + jsonWriter.Formatting = Formatting.Indented; + + while (jsonTextReader.Read()) + jsonWriter.WriteToken(jsonTextReader); + + jsonWriter.Flush(); + return stringWriter.ToString(); + } + + + [Benchmark] + public string TestJsonDocument() + { + var doc = JsonDocument.Parse(testJson); + + using var stream = new MemoryStream(); + var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }); + doc.WriteTo(writer); + writer.Flush(); + + return Encoding.UTF8.GetString(stream.ToArray()); + } + + + + } + + + public class Program + { + public static void Main() + { + BenchmarkRunner.Run(); + + // To prove they all provide the correct output + /* + var prettyPrint = new JsonPrettyPrint(); + Console.WriteLine("JsonConvert"); + Console.WriteLine("-----------"); + Console.WriteLine(prettyPrint.TestJsonConvert()); + + Console.WriteLine("JToken"); + Console.WriteLine("------"); + Console.WriteLine(prettyPrint.TestJTokenParse()); + + Console.WriteLine("ReaderWriter"); + Console.WriteLine("------------"); + Console.WriteLine(prettyPrint.TestReaderWriter()); + + Console.WriteLine("JsonDocument"); + Console.WriteLine("------------"); + Console.WriteLine(prettyPrint.TestJsonDocument()); + */ + } + } +} \ No newline at end of file diff --git a/PettingZoo.Core/Connection/ConnectionParams.cs b/PettingZoo.Core/Connection/ConnectionParams.cs index fb63f5e..44e55e1 100644 --- a/PettingZoo.Core/Connection/ConnectionParams.cs +++ b/PettingZoo.Core/Connection/ConnectionParams.cs @@ -17,5 +17,11 @@ Username = username; Password = password; } + + + public override string ToString() + { + return $"{Host}:{Port}{VirtualHost}"; + } } } diff --git a/PettingZoo.Core/Connection/DynamicConnection.cs b/PettingZoo.Core/Connection/DynamicConnection.cs new file mode 100644 index 0000000..ba9043b --- /dev/null +++ b/PettingZoo.Core/Connection/DynamicConnection.cs @@ -0,0 +1,100 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +namespace PettingZoo.Core.Connection +{ + public class DynamicConnection : IConnection + { + public Guid ConnectionId => currentConnection?.ConnectionId ?? Guid.Empty; + public ConnectionParams? ConnectionParams { get; private set; } + public ConnectionStatus Status { get; private set; } = ConnectionStatus.Disconnected; + public event EventHandler? StatusChanged; + + + private IConnection? currentConnection; + + + public async ValueTask DisposeAsync() + { + if (currentConnection != null) + await currentConnection.DisposeAsync(); + + GC.SuppressFinalize(this); + } + + + public void Connect() + { + CheckConnection(); + currentConnection.Connect(); + + } + + public async ValueTask Disconnect() + { + if (currentConnection == null) + return; + + var disconnectedConnectionId = currentConnection.ConnectionId; + await currentConnection.DisposeAsync(); + currentConnection = null; + + ConnectionStatusChanged(this, new StatusChangedEventArgs(disconnectedConnectionId, ConnectionStatus.Disconnected)); + } + + + public void SetConnection(IConnection connection) + { + if (currentConnection != null) + { + currentConnection.StatusChanged -= ConnectionStatusChanged; + ConnectionStatusChanged(this, new StatusChangedEventArgs(currentConnection.ConnectionId, ConnectionStatus.Disconnected)); + } + + currentConnection = connection; + + // Assume we get the new connection before Connect is called, thus before the status changes + if (currentConnection != null) + currentConnection.StatusChanged += ConnectionStatusChanged; + } + + + public ISubscriber Subscribe(string exchange, string routingKey) + { + CheckConnection(); + return currentConnection.Subscribe(exchange, routingKey); + } + + + public ISubscriber Subscribe() + { + CheckConnection(); + return currentConnection.Subscribe(); + } + + + public Task Publish(PublishMessageInfo messageInfo) + { + CheckConnection(); + return currentConnection.Publish(messageInfo); + } + + + private void ConnectionStatusChanged(object? sender, StatusChangedEventArgs e) + { + ConnectionParams = e.ConnectionParams; + Status = e.Status; + + StatusChanged?.Invoke(sender, e); + } + + + [MemberNotNull(nameof(currentConnection))] + private void CheckConnection() + { + if (currentConnection == null) + throw new InvalidOperationException("No current connection"); + } + } +} diff --git a/PettingZoo.Core/Connection/IConnection.cs b/PettingZoo.Core/Connection/IConnection.cs index f4ac798..6f49fd1 100644 --- a/PettingZoo.Core/Connection/IConnection.cs +++ b/PettingZoo.Core/Connection/IConnection.cs @@ -5,8 +5,15 @@ namespace PettingZoo.Core.Connection { public interface IConnection : IAsyncDisposable { + Guid ConnectionId { get; } + ConnectionParams? ConnectionParams { get; } + ConnectionStatus Status { get; } + event EventHandler StatusChanged; + + void Connect(); + ISubscriber Subscribe(string exchange, string routingKey); ISubscriber Subscribe(); @@ -25,14 +32,18 @@ namespace PettingZoo.Core.Connection public class StatusChangedEventArgs : EventArgs { + public Guid ConnectionId { get; } public ConnectionStatus Status { get; } - public string? Context { get; } + public ConnectionParams? ConnectionParams { get; } + public Exception? Exception { get; } - public StatusChangedEventArgs(ConnectionStatus status, string? context) + public StatusChangedEventArgs(Guid connectionId, ConnectionStatus status, ConnectionParams? connectionParams = null, Exception? exception = null) { + ConnectionId = connectionId; Status = status; - Context = context; + ConnectionParams = connectionParams; + Exception = exception; } } } diff --git a/PettingZoo.Core/ExportImport/Publisher/PublisherMessage.cs b/PettingZoo.Core/ExportImport/Publisher/PublisherMessage.cs new file mode 100644 index 0000000..9c930cd --- /dev/null +++ b/PettingZoo.Core/ExportImport/Publisher/PublisherMessage.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using PettingZoo.Core.Connection; + +namespace PettingZoo.Core.ExportImport.Publisher +{ + public enum PublisherMessageType + { + Raw, + Tapeti + } + + + + public class PublisherMessage : IEquatable + { + public PublisherMessageType MessageType { get; init; } + public bool SendToExchange { get; init; } + public string? Exchange { get; init; } + public string? RoutingKey { get; init; } + public string? Queue { get; init; } + public bool ReplyToNewSubscriber { get; init; } + public string? ReplyTo { get; init; } + + public RawPublisherMessage? RawPublisherMessage { get; init; } + public TapetiPublisherMessage? TapetiPublisherMessage { get; init; } + + + public bool Equals(PublisherMessage? other) + { + if (other == null) return false; + if (ReferenceEquals(this, other)) return true; + + return MessageType == other.MessageType && + SendToExchange == other.SendToExchange && + Exchange == other.Exchange && + RoutingKey == other.RoutingKey && + Queue == other.Queue && + ReplyToNewSubscriber == other.ReplyToNewSubscriber && + ReplyTo == other.ReplyTo && + Equals(RawPublisherMessage, other.RawPublisherMessage) && + Equals(TapetiPublisherMessage, other.TapetiPublisherMessage); + } + + + public override bool Equals(object? obj) + { + if (obj == null) return false; + if (ReferenceEquals(this, obj)) return true; + + return obj is PublisherMessage publisherMessage && Equals(publisherMessage); + } + + + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add((int)MessageType); + hashCode.Add(SendToExchange); + hashCode.Add(Exchange); + hashCode.Add(RoutingKey); + hashCode.Add(Queue); + hashCode.Add(ReplyToNewSubscriber); + hashCode.Add(ReplyTo); + hashCode.Add(RawPublisherMessage); + hashCode.Add(TapetiPublisherMessage); + return hashCode.ToHashCode(); + } + } + + + public class RawPublisherMessage : IEquatable + { + public MessageDeliveryMode DeliveryMode { get; init; } + + public string? ContentType { get; init; } + public string? CorrelationId { get; init; } + public string? AppId { get; init; } + public string? ContentEncoding { get; init; } + public string? Expiration { get; init; } + public string? MessageId { get; init; } + public string? Priority { get; init; } + public string? Timestamp { get; init; } + public string? TypeProperty { get; init; } + public string? UserId { get; init; } + public string? Payload { get; init; } + public bool EnableMacros { get; init; } + + public Dictionary? Headers { get; init; } + + + public bool Equals(RawPublisherMessage? other) + { + if (other == null) return false; + if (ReferenceEquals(this, other)) return true; + + return DeliveryMode == other.DeliveryMode && + ContentType == other.ContentType && + CorrelationId == other.CorrelationId && + AppId == other.AppId && + ContentEncoding == other.ContentEncoding && + Expiration == other.Expiration && + MessageId == other.MessageId && + Priority == other.Priority && + Timestamp == other.Timestamp && + TypeProperty == other.TypeProperty && + UserId == other.UserId && + Payload == other.Payload && + EnableMacros == other.EnableMacros && + HeadersEquals(other.Headers); + } + + + private bool HeadersEquals(Dictionary? other) + { + if (other == null) + return Headers == null || Headers.Count == 0; + + if (Headers == null) + return other.Count == 0; + + return other.OrderBy(h => h.Key).SequenceEqual(Headers.OrderBy(h => h.Key)); + } + + + public override bool Equals(object? obj) + { + if (obj == null) return false; + if (ReferenceEquals(this, obj)) return true; + + return obj is RawPublisherMessage rawPublisherMessage && Equals(rawPublisherMessage); + } + + + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add((int)DeliveryMode); + hashCode.Add(ContentType); + hashCode.Add(CorrelationId); + hashCode.Add(AppId); + hashCode.Add(ContentEncoding); + hashCode.Add(Expiration); + hashCode.Add(MessageId); + hashCode.Add(Priority); + hashCode.Add(Timestamp); + hashCode.Add(TypeProperty); + hashCode.Add(UserId); + hashCode.Add(Payload); + hashCode.Add(EnableMacros); + + if (Headers != null) + foreach (var (key, value) in Headers) + { + hashCode.Add(key); + hashCode.Add(value); + } + + return hashCode.ToHashCode(); + } + } + + + public class TapetiPublisherMessage : IEquatable + { + public string? CorrelationId { get; init; } + public string? Payload { get; init; } + public bool EnableMacros { get; init; } + public string? ClassName { get; init; } + public string? AssemblyName { get; init; } + + + public bool Equals(TapetiPublisherMessage? other) + { + if (other == null) return false; + if (ReferenceEquals(this, other)) return true; + + return CorrelationId == other.CorrelationId && + Payload == other.Payload && + EnableMacros == other.EnableMacros && + ClassName == other.ClassName && + AssemblyName == other.AssemblyName; + } + + + public override bool Equals(object? obj) + { + if (obj == null) return false; + if (ReferenceEquals(this, obj)) return true; + + return obj is TapetiPublisherMessage tapetiPublisherMessage && Equals(tapetiPublisherMessage); + } + + + public override int GetHashCode() + { + return HashCode.Combine(CorrelationId, Payload, EnableMacros, ClassName, AssemblyName); + } + } +} diff --git a/PettingZoo.Core/ExportImport/BaseProgressDecorator.cs b/PettingZoo.Core/ExportImport/Subscriber/BaseProgressDecorator.cs similarity index 95% rename from PettingZoo.Core/ExportImport/BaseProgressDecorator.cs rename to PettingZoo.Core/ExportImport/Subscriber/BaseProgressDecorator.cs index 8e02cc4..89dcf3f 100644 --- a/PettingZoo.Core/ExportImport/BaseProgressDecorator.cs +++ b/PettingZoo.Core/ExportImport/Subscriber/BaseProgressDecorator.cs @@ -1,6 +1,6 @@ using System; -namespace PettingZoo.Core.ExportImport +namespace PettingZoo.Core.ExportImport.Subscriber { public abstract class BaseProgressDecorator { diff --git a/PettingZoo.Core/ExportImport/ExportImportFormatProvider.cs b/PettingZoo.Core/ExportImport/Subscriber/ExportImportFormatProvider.cs similarity index 93% rename from PettingZoo.Core/ExportImport/ExportImportFormatProvider.cs rename to PettingZoo.Core/ExportImport/Subscriber/ExportImportFormatProvider.cs index 1482bf2..190a18c 100644 --- a/PettingZoo.Core/ExportImport/ExportImportFormatProvider.cs +++ b/PettingZoo.Core/ExportImport/Subscriber/ExportImportFormatProvider.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace PettingZoo.Core.ExportImport +namespace PettingZoo.Core.ExportImport.Subscriber { public class ExportImportFormatProvider : IExportImportFormatProvider { diff --git a/PettingZoo.Core/ExportImport/IExportImportFormat.cs b/PettingZoo.Core/ExportImport/Subscriber/IExportImportFormat.cs similarity index 92% rename from PettingZoo.Core/ExportImport/IExportImportFormat.cs rename to PettingZoo.Core/ExportImport/Subscriber/IExportImportFormat.cs index e586348..d7b9c94 100644 --- a/PettingZoo.Core/ExportImport/IExportImportFormat.cs +++ b/PettingZoo.Core/ExportImport/Subscriber/IExportImportFormat.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; using PettingZoo.Core.Connection; -namespace PettingZoo.Core.ExportImport +namespace PettingZoo.Core.ExportImport.Subscriber { public interface IExportImportFormat { diff --git a/PettingZoo.Core/ExportImport/IExportImportFormatProvider.cs b/PettingZoo.Core/ExportImport/Subscriber/IExportImportFormatProvider.cs similarity index 82% rename from PettingZoo.Core/ExportImport/IExportImportFormatProvider.cs rename to PettingZoo.Core/ExportImport/Subscriber/IExportImportFormatProvider.cs index 1991668..55ab49a 100644 --- a/PettingZoo.Core/ExportImport/IExportImportFormatProvider.cs +++ b/PettingZoo.Core/ExportImport/Subscriber/IExportImportFormatProvider.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace PettingZoo.Core.ExportImport +namespace PettingZoo.Core.ExportImport.Subscriber { public interface IExportImportFormatProvider { diff --git a/PettingZoo.Core/ExportImport/ImportSubscriber.cs b/PettingZoo.Core/ExportImport/Subscriber/ImportSubscriber.cs similarity index 95% rename from PettingZoo.Core/ExportImport/ImportSubscriber.cs rename to PettingZoo.Core/ExportImport/Subscriber/ImportSubscriber.cs index 49ffaca..dbfc831 100644 --- a/PettingZoo.Core/ExportImport/ImportSubscriber.cs +++ b/PettingZoo.Core/ExportImport/Subscriber/ImportSubscriber.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using PettingZoo.Core.Connection; -namespace PettingZoo.Core.ExportImport +namespace PettingZoo.Core.ExportImport.Subscriber { public class ImportSubscriber : ISubscriber { diff --git a/PettingZoo.Core/ExportImport/ListEnumerableProgressDecorator.cs b/PettingZoo.Core/ExportImport/Subscriber/ListEnumerableProgressDecorator.cs similarity index 98% rename from PettingZoo.Core/ExportImport/ListEnumerableProgressDecorator.cs rename to PettingZoo.Core/ExportImport/Subscriber/ListEnumerableProgressDecorator.cs index d74425a..4c63f66 100644 --- a/PettingZoo.Core/ExportImport/ListEnumerableProgressDecorator.cs +++ b/PettingZoo.Core/ExportImport/Subscriber/ListEnumerableProgressDecorator.cs @@ -2,7 +2,7 @@ using System.Collections; using System.Collections.Generic; -namespace PettingZoo.Core.ExportImport +namespace PettingZoo.Core.ExportImport.Subscriber { public class ListEnumerableProgressDecorator : BaseProgressDecorator, IEnumerable { diff --git a/PettingZoo.Core/ExportImport/StreamProgressDecorator.cs b/PettingZoo.Core/ExportImport/Subscriber/StreamProgressDecorator.cs similarity index 99% rename from PettingZoo.Core/ExportImport/StreamProgressDecorator.cs rename to PettingZoo.Core/ExportImport/Subscriber/StreamProgressDecorator.cs index b2abeaa..16eebe8 100644 --- a/PettingZoo.Core/ExportImport/StreamProgressDecorator.cs +++ b/PettingZoo.Core/ExportImport/Subscriber/StreamProgressDecorator.cs @@ -3,7 +3,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -namespace PettingZoo.Core.ExportImport +namespace PettingZoo.Core.ExportImport.Subscriber { public class StreamProgressDecorator : BaseProgressDecorator { diff --git a/PettingZoo.Core/PettingZoo.Core.csproj b/PettingZoo.Core/PettingZoo.Core.csproj index 6302265..5d0aac6 100644 --- a/PettingZoo.Core/PettingZoo.Core.csproj +++ b/PettingZoo.Core/PettingZoo.Core.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/PettingZoo.Core/Rendering/MessageBodyRenderer.cs b/PettingZoo.Core/Rendering/MessageBodyRenderer.cs index 4482da1..f184fa0 100644 --- a/PettingZoo.Core/Rendering/MessageBodyRenderer.cs +++ b/PettingZoo.Core/Rendering/MessageBodyRenderer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text; using Newtonsoft.Json; @@ -26,8 +27,18 @@ namespace PettingZoo.Core.Rendering var bodyText = Encoding.UTF8.GetString(body); try { - var obj = JsonConvert.DeserializeObject(bodyText); - return JsonConvert.SerializeObject(obj, Formatting.Indented); + using var stringReader = new StringReader(bodyText); + using var jsonTextReader = new JsonTextReader(stringReader); + using var stringWriter = new StringWriter(); + using var jsonWriter = new JsonTextWriter(stringWriter); + + jsonWriter.Formatting = Formatting.Indented; + + while (jsonTextReader.Read()) + jsonWriter.WriteToken(jsonTextReader); + + jsonWriter.Flush(); + return stringWriter.ToString(); } catch { diff --git a/PettingZoo.Core/Settings/IPublisherMessagesRepository.cs b/PettingZoo.Core/Settings/IPublisherMessagesRepository.cs new file mode 100644 index 0000000..51d438c --- /dev/null +++ b/PettingZoo.Core/Settings/IPublisherMessagesRepository.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using PettingZoo.Core.ExportImport.Publisher; + +namespace PettingZoo.Core.Settings +{ + public interface IPublisherMessagesRepository + { + // For now read everything into memory, you need quite a few and/or huge messsages before that becomes an issue + Task> GetStored(); + + Task Add(string displayName, PublisherMessage message); + Task Update(Guid id, string displayName, PublisherMessage message); + Task Delete(Guid id); + } + + + public class StoredPublisherMessage + { + public Guid Id { get; } + public string DisplayName { get; } + public PublisherMessage Message { get; } + + + public StoredPublisherMessage(Guid id, string displayName, PublisherMessage message) + { + Id = id; + DisplayName = displayName; + Message = message; + } + } +} \ No newline at end of file diff --git a/PettingZoo.RabbitMQ/PettingZoo.RabbitMQ.csproj b/PettingZoo.RabbitMQ/PettingZoo.RabbitMQ.csproj index 45d6771..ad52b6a 100644 --- a/PettingZoo.RabbitMQ/PettingZoo.RabbitMQ.csproj +++ b/PettingZoo.RabbitMQ/PettingZoo.RabbitMQ.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/PettingZoo.RabbitMQ/RabbitMQClientConnection.cs b/PettingZoo.RabbitMQ/RabbitMQClientConnection.cs index 9f790c8..1db1574 100644 --- a/PettingZoo.RabbitMQ/RabbitMQClientConnection.cs +++ b/PettingZoo.RabbitMQ/RabbitMQClientConnection.cs @@ -3,51 +3,56 @@ using System.Threading; using System.Threading.Tasks; using PettingZoo.Core.Connection; using RabbitMQ.Client; +using IConnection = RabbitMQ.Client.IConnection; namespace PettingZoo.RabbitMQ { public class RabbitMQClientConnection : Core.Connection.IConnection { + public Guid ConnectionId { get; } = Guid.NewGuid(); + public ConnectionParams? ConnectionParams { get; } + public ConnectionStatus Status { get; set; } + public event EventHandler? StatusChanged; + + private const int ConnectRetryDelay = 5000; private readonly CancellationTokenSource connectionTaskToken = new(); - private readonly Task connectionTask; + private Task? connectionTask; private readonly object connectionLock = new(); - private global::RabbitMQ.Client.IConnection? connection; - private IModel? model; + private IConnection? connection; - public event EventHandler? StatusChanged; - - public RabbitMQClientConnection(ConnectionParams connectionParams) { - connectionTask = Task.Factory.StartNew(() => TryConnection(connectionParams, connectionTaskToken.Token), CancellationToken.None); + ConnectionParams = connectionParams; } public async ValueTask DisposeAsync() { + GC.SuppressFinalize(this); + if (connectionTask == null) + return; + connectionTaskToken.Cancel(); if (!connectionTask.IsCompleted) await connectionTask; lock (connectionLock) { - if (model != null) - { - model.Dispose(); - model = null; - } - if (connection != null) { connection.Dispose(); connection = null; } } + } - GC.SuppressFinalize(this); + + public void Connect() + { + connectionTask = Task.Factory.StartNew(() => TryConnection(ConnectionParams!, connectionTaskToken.Token), CancellationToken.None); } @@ -67,6 +72,7 @@ namespace PettingZoo.RabbitMQ { lock (connectionLock) { + var model = connection?.CreateModel(); var subscriber = new RabbitMQClientSubscriber(model, exchange, routingKey); if (model != null) return subscriber; @@ -79,10 +85,10 @@ namespace PettingZoo.RabbitMQ lock (connectionLock) { - if (model == null) + if (connection == null) return; - subscriber.Connected(model); + subscriber.Connected(connection.CreateModel()); } StatusChanged -= ConnectSubscriber; @@ -97,12 +103,30 @@ namespace PettingZoo.RabbitMQ public Task Publish(PublishMessageInfo messageInfo) { - if (model == null) + IConnection? lockedConnection; + + lock (connectionLock) + { + lockedConnection = connection; + } + + if (lockedConnection == null) throw new InvalidOperationException("Not connected"); - model.BasicPublish(messageInfo.Exchange, messageInfo.RoutingKey, false, - RabbitMQClientPropertiesConverter.Convert(messageInfo.Properties, model.CreateBasicProperties()), - messageInfo.Body); + using (var model = lockedConnection.CreateModel()) + { + try + { + model.BasicPublish(messageInfo.Exchange, messageInfo.RoutingKey, false, + RabbitMQClientPropertiesConverter.Convert(messageInfo.Properties, + model.CreateBasicProperties()), + messageInfo.Body); + } + finally + { + model.Close(); + } + } return Task.CompletedTask; } @@ -119,22 +143,22 @@ namespace PettingZoo.RabbitMQ Password = connectionParams.Password }; - var statusContext = $"{connectionParams.Host}:{connectionParams.Port}{connectionParams.VirtualHost}"; - while (!cancellationToken.IsCancellationRequested) { - DoStatusChanged(ConnectionStatus.Connecting, statusContext); + DoStatusChanged(ConnectionStatus.Connecting); try { - connection = factory.CreateConnection(); - model = connection.CreateModel(); - - DoStatusChanged(ConnectionStatus.Connected, statusContext); + lock (connectionLock) + { + connection = factory.CreateConnection(); + } + + DoStatusChanged(ConnectionStatus.Connected); break; } catch (Exception e) { - DoStatusChanged(ConnectionStatus.Error, e.Message); + DoStatusChanged(ConnectionStatus.Error, e); try { @@ -148,9 +172,10 @@ namespace PettingZoo.RabbitMQ } - private void DoStatusChanged(ConnectionStatus status, string? context = null) + private void DoStatusChanged(ConnectionStatus status, Exception? exception = null) { - StatusChanged?.Invoke(this, new StatusChangedEventArgs(status, context)); + Status = status; + StatusChanged?.Invoke(this, new StatusChangedEventArgs(ConnectionId, status, ConnectionParams, exception)); } } } diff --git a/PettingZoo.Settings.LiteDB/LiteDBPublisherMessagesRepository.cs b/PettingZoo.Settings.LiteDB/LiteDBPublisherMessagesRepository.cs new file mode 100644 index 0000000..81c766e --- /dev/null +++ b/PettingZoo.Settings.LiteDB/LiteDBPublisherMessagesRepository.cs @@ -0,0 +1,80 @@ +using PettingZoo.Core.ExportImport.Publisher; +using PettingZoo.Core.Settings; + +namespace PettingZoo.Settings.LiteDB +{ + public class LiteDBPublisherMessagesRepository : BaseLiteDBRepository, IPublisherMessagesRepository + { + private const string CollectionMessages = "messages"; + + + public LiteDBPublisherMessagesRepository() : base(@"publisherMessages") + { + } + + + public async Task> GetStored() + { + using var database = GetDatabase(); + var collection = database.GetCollection(CollectionMessages); + + return (await collection.FindAllAsync()) + .Select(r => new StoredPublisherMessage(r.Id, r.DisplayName, r.Message)) + .ToArray(); + } + + + public async Task Add(string displayName, PublisherMessage message) + { + using var database = GetDatabase(); + var collection = database.GetCollection(CollectionMessages); + + var id = Guid.NewGuid(); + await collection.InsertAsync(PublisherMessageRecord.FromPublisherMessage(id, displayName, message)); + + return new StoredPublisherMessage(id, displayName, message); + } + + + public async Task Update(Guid id, string displayName, PublisherMessage message) + { + using var database = GetDatabase(); + var collection = database.GetCollection(CollectionMessages); + + await collection.UpdateAsync(PublisherMessageRecord.FromPublisherMessage(id, displayName, message)); + return new StoredPublisherMessage(id, displayName, message); + } + + + public async Task Delete(Guid id) + { + using var database = GetDatabase(); + var collection = database.GetCollection(CollectionMessages); + + await collection.DeleteAsync(id); + } + + + // ReSharper disable MemberCanBePrivate.Local - for LiteDB + // ReSharper disable PropertyCanBeMadeInitOnly.Local + private class PublisherMessageRecord + { + public Guid Id { get; set; } + public string DisplayName { get; set; } = null!; + public PublisherMessage Message { get; set; } = null!; + + + public static PublisherMessageRecord FromPublisherMessage(Guid id, string displayName, PublisherMessage message) + { + return new PublisherMessageRecord + { + Id = id, + DisplayName = displayName, + Message = message + }; + } + } + // ReSharper restore PropertyCanBeMadeInitOnly.Local + // ReSharper restore MemberCanBePrivate.Local + } +} \ No newline at end of file diff --git a/PettingZoo.Settings.LiteDB/PettingZoo.Settings.LiteDB.csproj b/PettingZoo.Settings.LiteDB/PettingZoo.Settings.LiteDB.csproj index f5fef95..e7d3208 100644 --- a/PettingZoo.Settings.LiteDB/PettingZoo.Settings.LiteDB.csproj +++ b/PettingZoo.Settings.LiteDB/PettingZoo.Settings.LiteDB.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/PettingZoo.Tapeti/Export/BaseTapetiCmdExportImportFormat.cs b/PettingZoo.Tapeti/ExportImport/BaseTapetiCmdExportImportFormat.cs similarity index 93% rename from PettingZoo.Tapeti/Export/BaseTapetiCmdExportImportFormat.cs rename to PettingZoo.Tapeti/ExportImport/BaseTapetiCmdExportImportFormat.cs index 6bfd721..cd54b2c 100644 --- a/PettingZoo.Tapeti/Export/BaseTapetiCmdExportImportFormat.cs +++ b/PettingZoo.Tapeti/ExportImport/BaseTapetiCmdExportImportFormat.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using Newtonsoft.Json.Linq; -using PettingZoo.Core.ExportImport; +using PettingZoo.Core.ExportImport.Subscriber; -namespace PettingZoo.Tapeti.Export +namespace PettingZoo.Tapeti.ExportImport { public abstract class BaseTapetiCmdExportImportFormat : IExportImportFormat { diff --git a/PettingZoo.Tapeti/Export/TapetiCmdExportFormat.cs b/PettingZoo.Tapeti/ExportImport/TapetiCmdExportFormat.cs similarity index 97% rename from PettingZoo.Tapeti/Export/TapetiCmdExportFormat.cs rename to PettingZoo.Tapeti/ExportImport/TapetiCmdExportFormat.cs index 7967827..fdce801 100644 --- a/PettingZoo.Tapeti/Export/TapetiCmdExportFormat.cs +++ b/PettingZoo.Tapeti/ExportImport/TapetiCmdExportFormat.cs @@ -8,10 +8,9 @@ using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PettingZoo.Core.Connection; -using PettingZoo.Core.ExportImport; +using PettingZoo.Core.ExportImport.Subscriber; - -namespace PettingZoo.Tapeti.Export +namespace PettingZoo.Tapeti.ExportImport { public class TapetiCmdExportFormat : BaseTapetiCmdExportImportFormat, IExportFormat { diff --git a/PettingZoo.Tapeti/Export/TapetiCmdImportExportStrings.Designer.cs b/PettingZoo.Tapeti/ExportImport/TapetiCmdImportExportStrings.Designer.cs similarity index 95% rename from PettingZoo.Tapeti/Export/TapetiCmdImportExportStrings.Designer.cs rename to PettingZoo.Tapeti/ExportImport/TapetiCmdImportExportStrings.Designer.cs index a9d3bf5..38103e4 100644 --- a/PettingZoo.Tapeti/Export/TapetiCmdImportExportStrings.Designer.cs +++ b/PettingZoo.Tapeti/ExportImport/TapetiCmdImportExportStrings.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace PettingZoo.Tapeti.Export { +namespace PettingZoo.Tapeti.ExportImport { using System; @@ -39,7 +39,7 @@ namespace PettingZoo.Tapeti.Export { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.Tapeti.Export.TapetiCmdImportExportStrings", typeof(TapetiCmdImportExportStrings).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.Tapeti.ExportImport.TapetiCmdImportExportStrings", typeof(TapetiCmdImportExportStrings).Assembly); resourceMan = temp; } return resourceMan; diff --git a/PettingZoo.Tapeti/Export/TapetiCmdImportExportStrings.resx b/PettingZoo.Tapeti/ExportImport/TapetiCmdImportExportStrings.resx similarity index 100% rename from PettingZoo.Tapeti/Export/TapetiCmdImportExportStrings.resx rename to PettingZoo.Tapeti/ExportImport/TapetiCmdImportExportStrings.resx diff --git a/PettingZoo.Tapeti/Export/TapetiCmdImportFormat.cs b/PettingZoo.Tapeti/ExportImport/TapetiCmdImportFormat.cs similarity index 97% rename from PettingZoo.Tapeti/Export/TapetiCmdImportFormat.cs rename to PettingZoo.Tapeti/ExportImport/TapetiCmdImportFormat.cs index 58f88d4..c5af72e 100644 --- a/PettingZoo.Tapeti/Export/TapetiCmdImportFormat.cs +++ b/PettingZoo.Tapeti/ExportImport/TapetiCmdImportFormat.cs @@ -7,9 +7,9 @@ using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using PettingZoo.Core.Connection; -using PettingZoo.Core.ExportImport; +using PettingZoo.Core.ExportImport.Subscriber; -namespace PettingZoo.Tapeti.Export +namespace PettingZoo.Tapeti.ExportImport { public class TapetiCmdImportFormat : BaseTapetiCmdExportImportFormat, IImportFormat { diff --git a/PettingZoo.Tapeti/PettingZoo.Tapeti.csproj b/PettingZoo.Tapeti/PettingZoo.Tapeti.csproj index 68c879d..c2fc81f 100644 --- a/PettingZoo.Tapeti/PettingZoo.Tapeti.csproj +++ b/PettingZoo.Tapeti/PettingZoo.Tapeti.csproj @@ -8,18 +8,19 @@ - - - - - - + + + + + + + - - - - - + + + + + @@ -33,7 +34,7 @@ True AssemblyParserStrings.resx - + True True TapetiCmdImportExportStrings.resx @@ -60,7 +61,7 @@ ResXFileCodeGenerator AssemblyParserStrings.Designer.cs - + ResXFileCodeGenerator TapetiCmdImportExportStrings.Designer.cs diff --git a/PettingZoo.Tapeti/PettingZoo.Tapeti_ooj5vuwa_wpftmp.csproj b/PettingZoo.Tapeti/PettingZoo.Tapeti_ooj5vuwa_wpftmp.csproj deleted file mode 100644 index 9e25216..0000000 --- a/PettingZoo.Tapeti/PettingZoo.Tapeti_ooj5vuwa_wpftmp.csproj +++ /dev/null @@ -1,325 +0,0 @@ - - - PettingZoo.Tapeti - obj\Debug\ - obj\ - P:\Development\PettingZoo\PettingZoo.Tapeti\obj\ - <_TargetAssemblyProjectName>PettingZoo.Tapeti - - - - net6.0-windows - 0.1 - true - enable - - - - - - - - - - - - - - - - - - - - - True - True - AssemblyParserStrings.resx - - - True - True - TapetiCmdImportExportStrings.resx - - - True - True - TapetiClassLibraryExampleGeneratorStrings.resx - - - True - True - ClassSelectionStrings.resx - - - True - True - PackageSelectionStrings.resx - - - - - ResXFileCodeGenerator - AssemblyParserStrings.Designer.cs - - - ResXFileCodeGenerator - TapetiCmdImportExportStrings.Designer.cs - - - ResXFileCodeGenerator - TapetiClassLibraryExampleGeneratorStrings.Designer.cs - - - PublicResXFileCodeGenerator - ClassSelectionStrings.Designer.cs - - - PublicResXFileCodeGenerator - PackageSelectionStrings.Designer.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/PettingZoo.Test/PettingZoo.Test.csproj b/PettingZoo.Test/PettingZoo.Test.csproj index cf3907a..69bda7a 100644 --- a/PettingZoo.Test/PettingZoo.Test.csproj +++ b/PettingZoo.Test/PettingZoo.Test.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PettingZoo.WPF/Controls/AutoOverflowToolBar.cs b/PettingZoo.WPF/Controls/AutoOverflowToolBar.cs new file mode 100644 index 0000000..2d531ec --- /dev/null +++ b/PettingZoo.WPF/Controls/AutoOverflowToolBar.cs @@ -0,0 +1,26 @@ +using System.Windows; +using System.Windows.Controls; + +namespace PettingZoo.WPF.Controls +{ + public class AutoOverflowToolBar : ToolBar + { + public AutoOverflowToolBar() + { + Loaded += (_, _) => + { + // Hide overflow arrow on the right side of the toolbar when not required + if (Template.FindName("OverflowButton", this) is not FrameworkElement overflowButton) + return; + + if (!overflowButton.IsEnabled) + overflowButton.Visibility = Visibility.Hidden; + + overflowButton.IsEnabledChanged += (_, _) => + { + overflowButton.Visibility = overflowButton.IsEnabled ? Visibility.Visible : Visibility.Hidden; + }; + }; + } + } +} diff --git a/PettingZoo.WPF/PettingZoo.WPF.csproj b/PettingZoo.WPF/PettingZoo.WPF.csproj index 3adc030..ef051e4 100644 --- a/PettingZoo.WPF/PettingZoo.WPF.csproj +++ b/PettingZoo.WPF/PettingZoo.WPF.csproj @@ -7,7 +7,7 @@ - + diff --git a/PettingZoo.WPF/ValueConverters/SameReferenceConverter.cs b/PettingZoo.WPF/ValueConverters/SameReferenceConverter.cs new file mode 100644 index 0000000..ea0ff70 --- /dev/null +++ b/PettingZoo.WPF/ValueConverters/SameReferenceConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace PettingZoo.WPF.ValueConverters +{ + public class SameReferenceConverter : IMultiValueConverter + { + public object Convert(object?[] values, Type targetType, object parameter, CultureInfo culture) + { + return ReferenceEquals(values[0], values[1]); + } + + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/PettingZoo.sln b/PettingZoo.sln index e840afc..e606ce1 100644 --- a/PettingZoo.sln +++ b/PettingZoo.sln @@ -20,7 +20,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.Settings.LiteDB" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.WPF", "PettingZoo.WPF\PettingZoo.WPF.csproj", "{E6617B69-2AC4-4056-B801-DD32E2374B71}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PettingZoo.Test", "PettingZoo.Test\PettingZoo.Test.csproj", "{3DD7F8D5-2CEE-414D-AC9C-9F395568B79F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.Test", "PettingZoo.Test\PettingZoo.Test.csproj", "{3DD7F8D5-2CEE-414D-AC9C-9F395568B79F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PettingZoo.Benchmark", "PettingZoo.Benchmark\PettingZoo.Benchmark.csproj", "{C25BC83A-D302-46D2-97F6-5F888B824E2D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -56,6 +58,10 @@ Global {3DD7F8D5-2CEE-414D-AC9C-9F395568B79F}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DD7F8D5-2CEE-414D-AC9C-9F395568B79F}.Release|Any CPU.ActiveCfg = Release|Any CPU {3DD7F8D5-2CEE-414D-AC9C-9F395568B79F}.Release|Any CPU.Build.0 = Release|Any CPU + {C25BC83A-D302-46D2-97F6-5F888B824E2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C25BC83A-D302-46D2-97F6-5F888B824E2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C25BC83A-D302-46D2-97F6-5F888B824E2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C25BC83A-D302-46D2-97F6-5F888B824E2D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PettingZoo/Images/Delete.svg b/PettingZoo/Images/Delete.svg new file mode 100644 index 0000000..557bc3c --- /dev/null +++ b/PettingZoo/Images/Delete.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/PettingZoo/Images/Publish.svg b/PettingZoo/Images/Publish.svg index 1702435..67cd318 100644 --- a/PettingZoo/Images/Publish.svg +++ b/PettingZoo/Images/Publish.svg @@ -1,32 +1,53 @@ - - - - - - - - - - - - - - - - - - + + + + + + - - - + + + - - + + diff --git a/PettingZoo/Images/Save.svg b/PettingZoo/Images/Save.svg new file mode 100644 index 0000000..65b293b --- /dev/null +++ b/PettingZoo/Images/Save.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/PettingZoo/Images/SaveAs.svg b/PettingZoo/Images/SaveAs.svg new file mode 100644 index 0000000..f0630c9 --- /dev/null +++ b/PettingZoo/Images/SaveAs.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/PettingZoo/Images/Subscribe.svg b/PettingZoo/Images/Subscribe.svg index 0654459..c518099 100644 --- a/PettingZoo/Images/Subscribe.svg +++ b/PettingZoo/Images/Subscribe.svg @@ -1,32 +1,29 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + diff --git a/PettingZoo/PettingZoo.csproj b/PettingZoo/PettingZoo.csproj index cc761d5..ef09e96 100644 --- a/PettingZoo/PettingZoo.csproj +++ b/PettingZoo/PettingZoo.csproj @@ -20,6 +20,7 @@ + @@ -29,6 +30,8 @@ + + @@ -36,6 +39,7 @@ + @@ -47,17 +51,19 @@ + + - - - - - - - + + + + + + + @@ -75,10 +81,10 @@ - + True True - ConnectionDisplayNameStrings.resx + InputDialogStrings.resx ConnectionWindowStrings.resx @@ -100,6 +106,11 @@ True PayloadEditorStrings.resx + + True + True + StoredPublisherMessagesStrings.resx + TapetiPublisherViewStrings.resx True @@ -131,9 +142,9 @@ - + PublicResXFileCodeGenerator - ConnectionDisplayNameStrings.Designer.cs + InputDialogStrings.Designer.cs ConnectionWindowStrings.Designer.cs @@ -151,6 +162,10 @@ PublicResXFileCodeGenerator PayloadEditorStrings.Designer.cs + + ResXFileCodeGenerator + StoredPublisherMessagesStrings.Designer.cs + TapetiPublisherViewStrings.Designer.cs PublicResXFileCodeGenerator diff --git a/PettingZoo/Program.cs b/PettingZoo/Program.cs index f0f717a..6bdc1fe 100644 --- a/PettingZoo/Program.cs +++ b/PettingZoo/Program.cs @@ -4,18 +4,19 @@ using System.IO; using System.Windows; using System.Windows.Markup; using PettingZoo.Core.Connection; -using PettingZoo.Core.ExportImport; +using PettingZoo.Core.ExportImport.Subscriber; using PettingZoo.Core.Generator; using PettingZoo.Core.Macros; using PettingZoo.Core.Settings; using PettingZoo.RabbitMQ; using PettingZoo.Settings.LiteDB; using PettingZoo.Tapeti; -using PettingZoo.Tapeti.Export; +using PettingZoo.Tapeti.ExportImport; using PettingZoo.UI.Connection; using PettingZoo.UI.Main; using PettingZoo.UI.Subscribe; using PettingZoo.UI.Tab; +using PettingZoo.UI.Tab.Publisher; using Serilog; using SimpleInjector; @@ -82,10 +83,12 @@ namespace PettingZoo container.Register(); container.Register(); container.Register(); + container.RegisterSingleton(); container.Register(); container.RegisterSingleton(); container.Register(); container.RegisterSingleton(); + container.RegisterSingleton(); container.RegisterInstance(new ExportImportFormatProvider( new TapetiCmdExportFormat(), diff --git a/PettingZoo/TODO.md b/PettingZoo/TODO.md index f0312f5..e9f26ef 100644 --- a/PettingZoo/TODO.md +++ b/PettingZoo/TODO.md @@ -8,5 +8,4 @@ Should-have Nice-to-have ------------ -- Save / load publisher messages (either as templates or to disk) - Tapeti: fetch NuGet dependencies to improve the chances of succesfully loading the assembly, instead of the current "extraAssembliesPaths" workaround diff --git a/PettingZoo/UI/Connection/ConnectionDisplayNameDialog.xaml b/PettingZoo/UI/Connection/ConnectionDisplayNameDialog.xaml deleted file mode 100644 index e76c9de..0000000 --- a/PettingZoo/UI/Connection/ConnectionDisplayNameDialog.xaml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - diff --git a/PettingZoo/UI/Connection/ConnectionViewModel.cs b/PettingZoo/UI/Connection/ConnectionViewModel.cs index 0e9e445..05db2f4 100644 --- a/PettingZoo/UI/Connection/ConnectionViewModel.cs +++ b/PettingZoo/UI/Connection/ConnectionViewModel.cs @@ -262,7 +262,7 @@ namespace PettingZoo.UI.Connection // TODO create and enforce unique name? var displayName = SelectedStoredConnection != null && SelectedStoredConnection.Id != Guid.Empty ? SelectedStoredConnection.DisplayName : ""; - if (!ConnectionDisplayNameDialog.Execute(ref displayName)) + if (!InputDialog.Execute(ref displayName, ConnectionWindowStrings.ProfileNameDialogTitle)) return; var storedConnectionSettings = await connectionSettingsRepository.Add(displayName, StorePassword, ToModel()); diff --git a/PettingZoo/UI/Connection/ConnectionWindowStrings.Designer.cs b/PettingZoo/UI/Connection/ConnectionWindowStrings.Designer.cs index 2101f00..4288696 100644 --- a/PettingZoo/UI/Connection/ConnectionWindowStrings.Designer.cs +++ b/PettingZoo/UI/Connection/ConnectionWindowStrings.Designer.cs @@ -213,6 +213,15 @@ namespace PettingZoo.UI.Connection { } } + /// + /// Looks up a localized string similar to Profile name. + /// + public static string ProfileNameDialogTitle { + get { + return ResourceManager.GetString("ProfileNameDialogTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Connection parameters. /// diff --git a/PettingZoo/UI/Connection/ConnectionWindowStrings.resx b/PettingZoo/UI/Connection/ConnectionWindowStrings.resx index 5b739e5..b9d96c5 100644 --- a/PettingZoo/UI/Connection/ConnectionWindowStrings.resx +++ b/PettingZoo/UI/Connection/ConnectionWindowStrings.resx @@ -112,10 +112,10 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Cancel @@ -168,6 +168,9 @@ <New connection> + + Profile name + Connection parameters diff --git a/PettingZoo/UI/InputDialog.xaml b/PettingZoo/UI/InputDialog.xaml new file mode 100644 index 0000000..932eba8 --- /dev/null +++ b/PettingZoo/UI/InputDialog.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/PettingZoo/UI/Connection/ConnectionDisplayNameDialog.xaml.cs b/PettingZoo/UI/InputDialog.xaml.cs similarity index 55% rename from PettingZoo/UI/Connection/ConnectionDisplayNameDialog.xaml.cs rename to PettingZoo/UI/InputDialog.xaml.cs index 000287e..1d6c9ba 100644 --- a/PettingZoo/UI/Connection/ConnectionDisplayNameDialog.xaml.cs +++ b/PettingZoo/UI/InputDialog.xaml.cs @@ -1,18 +1,19 @@ using System.Linq; using System.Windows; -namespace PettingZoo.UI.Connection +namespace PettingZoo.UI { /// - /// Interaction logic for ConnectionDisplayNameDialog.xaml + /// Interaction logic for InputDialog.xaml /// - public partial class ConnectionDisplayNameDialog + public partial class InputDialog { - public static bool Execute(ref string displayName) + public static bool Execute(ref string value, string title) { - var viewModel = new ConnectionDisplayNameViewModel + var viewModel = new InputDialogViewModel { - DisplayName = displayName + Value = value, + Title = title }; @@ -20,7 +21,7 @@ namespace PettingZoo.UI.Connection .Cast() .FirstOrDefault(applicationWindow => applicationWindow.IsActive); - var window = new ConnectionDisplayNameDialog(viewModel) + var window = new InputDialog(viewModel) { Owner = activeWindow ?? Application.Current.MainWindow }; @@ -28,12 +29,12 @@ namespace PettingZoo.UI.Connection if (!window.ShowDialog().GetValueOrDefault()) return false; - displayName = viewModel.DisplayName; + value = viewModel.Value; return true; } - public ConnectionDisplayNameDialog(ConnectionDisplayNameViewModel viewModel) + public InputDialog(InputDialogViewModel viewModel) { viewModel.OkClick += (_, _) => { @@ -43,7 +44,7 @@ namespace PettingZoo.UI.Connection DataContext = viewModel; InitializeComponent(); - DisplayNameTextBox.CaretIndex = DisplayNameTextBox.Text.Length; + ValueTextBox.CaretIndex = ValueTextBox.Text.Length; } } } diff --git a/PettingZoo/UI/Connection/ConnectionDisplayNameStrings.Designer.cs b/PettingZoo/UI/InputDialogStrings.Designer.cs similarity index 86% rename from PettingZoo/UI/Connection/ConnectionDisplayNameStrings.Designer.cs rename to PettingZoo/UI/InputDialogStrings.Designer.cs index de2828b..a89015e 100644 --- a/PettingZoo/UI/Connection/ConnectionDisplayNameStrings.Designer.cs +++ b/PettingZoo/UI/InputDialogStrings.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace PettingZoo.UI.Connection { +namespace PettingZoo.UI { using System; @@ -22,14 +22,14 @@ namespace PettingZoo.UI.Connection { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class ConnectionDisplayNameStrings { + public class InputDialogStrings { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal ConnectionDisplayNameStrings() { + internal InputDialogStrings() { } /// @@ -39,7 +39,7 @@ namespace PettingZoo.UI.Connection { public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Connection.ConnectionDisplayNameStrings", typeof(ConnectionDisplayNameStrings).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.InputDialogStrings", typeof(InputDialogStrings).Assembly); resourceMan = temp; } return resourceMan; @@ -77,14 +77,5 @@ namespace PettingZoo.UI.Connection { return ResourceManager.GetString("ButtonOK", resourceCulture); } } - - /// - /// Looks up a localized string similar to Profile name. - /// - public static string WindowTitle { - get { - return ResourceManager.GetString("WindowTitle", resourceCulture); - } - } } } diff --git a/PettingZoo/UI/Connection/ConnectionDisplayNameStrings.resx b/PettingZoo/UI/InputDialogStrings.resx similarity index 96% rename from PettingZoo/UI/Connection/ConnectionDisplayNameStrings.resx rename to PettingZoo/UI/InputDialogStrings.resx index bc89e48..73ce2f1 100644 --- a/PettingZoo/UI/Connection/ConnectionDisplayNameStrings.resx +++ b/PettingZoo/UI/InputDialogStrings.resx @@ -112,10 +112,10 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Cancel @@ -123,7 +123,4 @@ OK - - Profile name - \ No newline at end of file diff --git a/PettingZoo/UI/Connection/ConnectionDisplayNameViewModel.cs b/PettingZoo/UI/InputDialogViewModel.cs similarity index 50% rename from PettingZoo/UI/Connection/ConnectionDisplayNameViewModel.cs rename to PettingZoo/UI/InputDialogViewModel.cs index 9594bfb..b5d1903 100644 --- a/PettingZoo/UI/Connection/ConnectionDisplayNameViewModel.cs +++ b/PettingZoo/UI/InputDialogViewModel.cs @@ -2,27 +2,36 @@ using System.Windows.Input; using PettingZoo.WPF.ViewModel; -namespace PettingZoo.UI.Connection +namespace PettingZoo.UI { - public class ConnectionDisplayNameViewModel : BaseViewModel + public class InputDialogViewModel : BaseViewModel { - private string displayName = ""; + private string title = ""; + private string value = ""; private readonly DelegateCommand okCommand; - public string DisplayName + public string Title { - get => displayName; - set => SetField(ref displayName, value, delegateCommandsChanged: new [] { okCommand }); + get => title; + set => SetField(ref title, value); } + + public string Value + { + get => value; + set => SetField(ref this.value, value, delegateCommandsChanged: new [] { okCommand }); + } + + public ICommand OkCommand => okCommand; public event EventHandler? OkClick; - public ConnectionDisplayNameViewModel() + public InputDialogViewModel() { okCommand = new DelegateCommand(OkExecute, OkCanExecute); } @@ -36,7 +45,7 @@ namespace PettingZoo.UI.Connection private bool OkCanExecute() { - return !string.IsNullOrWhiteSpace(DisplayName); + return !string.IsNullOrWhiteSpace(Value); } } } diff --git a/PettingZoo/UI/Main/MainWindow.xaml b/PettingZoo/UI/Main/MainWindow.xaml index 3ea2195..d9a638e 100644 --- a/PettingZoo/UI/Main/MainWindow.xaml +++ b/PettingZoo/UI/Main/MainWindow.xaml @@ -7,6 +7,7 @@ xmlns:tab="clr-namespace:PettingZoo.UI.Tab" xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" xmlns:ui="clr-namespace:PettingZoo.UI" + xmlns:controls="clr-namespace:PettingZoo.WPF.Controls;assembly=PettingZoo.WPF" mc:Ignorable="d" d:DataContext="{d:DesignInstance main:DesignTimeMainWindowViewModel, IsDesignTimeCreatable=True}" Width="800" @@ -24,7 +25,7 @@ - + + diff --git a/PettingZoo/UI/Main/MainWindow.xaml.cs b/PettingZoo/UI/Main/MainWindow.xaml.cs index 84e4e90..a769730 100644 --- a/PettingZoo/UI/Main/MainWindow.xaml.cs +++ b/PettingZoo/UI/Main/MainWindow.xaml.cs @@ -5,7 +5,7 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using PettingZoo.Core.Connection; -using PettingZoo.Core.ExportImport; +using PettingZoo.Core.ExportImport.Subscriber; using PettingZoo.UI.Connection; using PettingZoo.UI.Subscribe; using PettingZoo.UI.Tab; @@ -139,18 +139,6 @@ namespace PettingZoo.UI.Main public double TabWidth => SubscriberTabs.ActualWidth; public double TabHeight => SubscriberTabs.ActualHeight; - - private void Toolbar_Loaded(object sender, RoutedEventArgs e) - { - // Hide arrow on the right side of the toolbar - var toolBar = sender as ToolBar; - - if (toolBar?.Template.FindName("OverflowGrid", toolBar) is FrameworkElement overflowGrid) - overflowGrid.Visibility = Visibility.Collapsed; - - if (toolBar?.Template.FindName("MainPanelBorder", toolBar) is FrameworkElement mainPanelBorder) - mainPanelBorder.Margin = new Thickness(0); - } } #pragma warning restore CA1001 } diff --git a/PettingZoo/UI/Main/MainWindowStrings.Designer.cs b/PettingZoo/UI/Main/MainWindowStrings.Designer.cs index fb45c3f..09bb89b 100644 --- a/PettingZoo/UI/Main/MainWindowStrings.Designer.cs +++ b/PettingZoo/UI/Main/MainWindowStrings.Designer.cs @@ -160,7 +160,7 @@ namespace PettingZoo.UI.Main { } /// - /// Looks up a localized string similar to Connected. + /// Looks up a localized string similar to Connected to {0}. /// public static string StatusConnected { get { diff --git a/PettingZoo/UI/Main/MainWindowStrings.resx b/PettingZoo/UI/Main/MainWindowStrings.resx index 7825578..b567899 100644 --- a/PettingZoo/UI/Main/MainWindowStrings.resx +++ b/PettingZoo/UI/Main/MainWindowStrings.resx @@ -151,7 +151,7 @@ Importing messages... - Connected + Connected to {0} Connecting to {0}... diff --git a/PettingZoo/UI/Main/MainWindowViewModel.cs b/PettingZoo/UI/Main/MainWindowViewModel.cs index e7c190a..ea6a13a 100644 --- a/PettingZoo/UI/Main/MainWindowViewModel.cs +++ b/PettingZoo/UI/Main/MainWindowViewModel.cs @@ -9,7 +9,7 @@ using System.Windows; using System.Windows.Forms; using System.Windows.Input; using PettingZoo.Core.Connection; -using PettingZoo.Core.ExportImport; +using PettingZoo.Core.ExportImport.Subscriber; using PettingZoo.UI.Connection; using PettingZoo.UI.Subscribe; using PettingZoo.UI.Tab; @@ -43,7 +43,7 @@ namespace PettingZoo.UI.Main private readonly IExportImportFormatProvider exportImportFormatProvider; private SubscribeDialogParams? subscribeDialogParams; - private IConnection? connection; + private readonly DynamicConnection connection = new(); private string connectionStatus; private ITab? activeTab; private readonly Dictionary undockedTabs = new(); @@ -141,15 +141,15 @@ namespace PettingZoo.UI.Main closeTabCommand = new DelegateCommand(CloseTabExecute, HasActiveTabCanExecute); undockTabCommand = new DelegateCommand(UndockTabExecute, HasActiveTabCanExecute); importCommand = new DelegateCommand(ImportExecute); + + connection.StatusChanged += ConnectionStatusChanged; } public async ValueTask DisposeAsync() { GC.SuppressFinalize(this); - - if (connection != null) - await connection.DisposeAsync(); + await connection.DisposeAsync(); } @@ -159,13 +159,9 @@ namespace PettingZoo.UI.Main if (connectionSettings == null) return; - if (connection != null) - await connection.DisposeAsync(); - - connection = connectionFactory.CreateConnection(new ConnectionParams( + connection.SetConnection(connectionFactory.CreateConnection(new ConnectionParams( connectionSettings.Host, connectionSettings.VirtualHost, connectionSettings.Port, - connectionSettings.Username, connectionSettings.Password)); - connection.StatusChanged += ConnectionStatusChanged; + connectionSettings.Username, connectionSettings.Password))); if (connectionSettings.Subscribe) { @@ -173,40 +169,22 @@ namespace PettingZoo.UI.Main tabFactory.CreateSubscriberTab(connection, subscriber); } + connection.Connect(); ConnectionChanged(); } private async void DisconnectExecute() { - Tabs.Clear(); - - var capturedUndockedTabs = undockedTabs.ToList(); - undockedTabs.Clear(); - - foreach (var undockedTab in capturedUndockedTabs) - undockedTab.Value.Close(); - - RaisePropertyChanged(nameof(NoTabsVisibility)); - undockTabCommand.RaiseCanExecuteChanged(); - - if (connection != null) - { - await connection.DisposeAsync(); - connection = null; - } - - ConnectionStatus = GetConnectionStatus(null); - ConnectionStatusType = ConnectionStatusType.Error; - ConnectionChanged(); + await connection.Disconnect(); } private void SubscribeExecute() { - if (connection == null) + if (connection.Status != Core.Connection.ConnectionStatus.Connected) return; - + var newParams = subscribeDialog.Show(subscribeDialogParams); if (newParams == null) return; @@ -220,16 +198,16 @@ namespace PettingZoo.UI.Main private void PublishExecute() { - if (connection == null) + if (connection.Status != Core.Connection.ConnectionStatus.Connected) return; - + tabFactory.CreatePublisherTab(connection); } private bool IsConnectedCanExecute() { - return connection != null; + return connection.Status == Core.Connection.ConnectionStatus.Connected; } @@ -419,6 +397,8 @@ namespace PettingZoo.UI.Main Core.Connection.ConnectionStatus.Connecting => ConnectionStatusType.Connecting, _ => ConnectionStatusType.Error }; + + Application.Current.Dispatcher.BeginInvoke(ConnectionChanged); } @@ -427,9 +407,9 @@ namespace PettingZoo.UI.Main { return args?.Status switch { - Core.Connection.ConnectionStatus.Connecting => string.Format(MainWindowStrings.StatusConnecting, args.Context), - Core.Connection.ConnectionStatus.Connected => string.Format(MainWindowStrings.StatusConnected, args.Context), - Core.Connection.ConnectionStatus.Error => string.Format(MainWindowStrings.StatusError, args.Context), + Core.Connection.ConnectionStatus.Connecting => string.Format(MainWindowStrings.StatusConnecting, args.ConnectionParams), + Core.Connection.ConnectionStatus.Connected => string.Format(MainWindowStrings.StatusConnected, args.ConnectionParams), + Core.Connection.ConnectionStatus.Error => string.Format(MainWindowStrings.StatusError, args.Exception?.Message), Core.Connection.ConnectionStatus.Disconnected => MainWindowStrings.StatusDisconnected, _ => MainWindowStrings.StatusDisconnected }; diff --git a/PettingZoo/UI/Tab/ITabFactory.cs b/PettingZoo/UI/Tab/ITabFactory.cs index 78f5b8a..23f105b 100644 --- a/PettingZoo/UI/Tab/ITabFactory.cs +++ b/PettingZoo/UI/Tab/ITabFactory.cs @@ -4,7 +4,7 @@ namespace PettingZoo.UI.Tab { public interface ITabFactory { - void CreateSubscriberTab(IConnection? connection, ISubscriber subscriber); + void CreateSubscriberTab(IConnection connection, ISubscriber subscriber); string CreateReplySubscriberTab(IConnection connection); void CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null); } diff --git a/PettingZoo/UI/Tab/Publisher/PayloadEditorControl.xaml.cs b/PettingZoo/UI/Tab/Publisher/PayloadEditorControl.xaml.cs index 45925fc..64ded54 100644 --- a/PettingZoo/UI/Tab/Publisher/PayloadEditorControl.xaml.cs +++ b/PettingZoo/UI/Tab/Publisher/PayloadEditorControl.xaml.cs @@ -123,16 +123,15 @@ namespace PettingZoo.UI.Tab.Publisher } - private IPayloadMacroProcessor? macroProcessor; public IPayloadMacroProcessor? MacroProcessor { - get => macroProcessor; + get => viewModel.MacroProcessor; set { - if (value == macroProcessor) + if (value == viewModel.MacroProcessor) return; - macroProcessor = value; + viewModel.MacroProcessor = value; UpdateMacroContextMenu(); } } @@ -266,10 +265,10 @@ namespace PettingZoo.UI.Tab.Publisher { ContextMenuInsertMacro.Items.Clear(); - if (macroProcessor == null) + if (MacroProcessor == null) return; - foreach (var macro in macroProcessor.Macros) + foreach (var macro in MacroProcessor.Macros) { var macroMenuItem = new MenuItem { diff --git a/PettingZoo/UI/Tab/Publisher/PayloadEditorViewModel.cs b/PettingZoo/UI/Tab/Publisher/PayloadEditorViewModel.cs index d531539..8d9dbff 100644 --- a/PettingZoo/UI/Tab/Publisher/PayloadEditorViewModel.cs +++ b/PettingZoo/UI/Tab/Publisher/PayloadEditorViewModel.cs @@ -2,10 +2,10 @@ using System.ComponentModel; using System.Reactive.Linq; using System.Windows; -using System.Windows.Input; using ICSharpCode.AvalonEdit.Highlighting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using PettingZoo.Core.Macros; using PettingZoo.Core.Validation; using PettingZoo.WPF.ViewModel; @@ -159,6 +159,7 @@ namespace PettingZoo.UI.Tab.Publisher public IPayloadValidator? Validator { get; set; } + public IPayloadMacroProcessor? MacroProcessor { get; set; } public PayloadEditorViewModel() @@ -166,7 +167,7 @@ namespace PettingZoo.UI.Tab.Publisher var observable = Observable.FromEventPattern( h => PropertyChanged += h, h => PropertyChanged -= h) - .Where(e => e.EventArgs.PropertyName == nameof(Payload)); + .Where(e => e.EventArgs.PropertyName is nameof(Payload) or nameof(EnableMacros)); observable .Subscribe(_ => ValidatingPayload()); @@ -204,14 +205,18 @@ namespace PettingZoo.UI.Tab.Publisher { if (!string.IsNullOrEmpty(Payload)) { + var validatePayload = EnableMacros && MacroProcessor != null + ? MacroProcessor.Apply(Payload) + : Payload; + if (Validator != null && Validator.CanValidate()) { - Validator.Validate(payload); + Validator.Validate(validatePayload); ValidationInfo = new ValidationInfo(ValidationStatus.Ok); } else { - JToken.Parse(Payload); + JToken.Parse(validatePayload); ValidationInfo = new ValidationInfo(ValidationStatus.OkSyntax); } } diff --git a/PettingZoo/UI/Tab/Publisher/PublisherView.xaml b/PettingZoo/UI/Tab/Publisher/PublisherView.xaml index 03ba2aa..0d2b603 100644 --- a/PettingZoo/UI/Tab/Publisher/PublisherView.xaml +++ b/PettingZoo/UI/Tab/Publisher/PublisherView.xaml @@ -6,73 +6,168 @@ xmlns:res="clr-namespace:PettingZoo.UI.Tab.Publisher" xmlns:controls="clr-namespace:PettingZoo.WPF.Controls;assembly=PettingZoo.WPF" xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" + xmlns:valueConverters="clr-namespace:PettingZoo.WPF.ValueConverters;assembly=PettingZoo.WPF" mc:Ignorable="d" - d:DesignHeight="450" + d:DesignHeight="1200" d:DesignWidth="800" d:DataContext="{d:DesignInstance res:DesignTimePublisherViewModel, IsDesignTimeCreatable=True}" Background="White"> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + - - - - - - + + + + + - + + - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs b/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs index a8dbc2f..9919264 100644 --- a/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs +++ b/PettingZoo/UI/Tab/Publisher/PublisherViewModel.cs @@ -1,27 +1,33 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; +using System.Windows.Forms; using System.Windows.Input; +using System.Windows.Threading; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using PettingZoo.Core.Connection; +using PettingZoo.Core.ExportImport.Publisher; using PettingZoo.Core.Generator; using PettingZoo.Core.Macros; +using PettingZoo.Core.Settings; using PettingZoo.WPF.ViewModel; +using Application = System.Windows.Application; +using UserControl = System.Windows.Controls.UserControl; namespace PettingZoo.UI.Tab.Publisher { - public enum MessageType - { - Raw, - Tapeti - } - - - public class PublisherViewModel : BaseViewModel, ITabToolbarCommands, ITabHostWindowNotify, IPublishDestination + public class PublisherViewModel : BaseViewModel, IDisposable, ITabToolbarCommands, ITabHostWindowNotify, IPublishDestination { private readonly IConnection connection; private readonly IExampleGenerator exampleGenerator; private readonly IPayloadMacroProcessor payloadMacroProcessor; + private readonly StoredPublisherMessagesViewModel storedPublisherMessagesViewModel; private readonly ITabFactory tabFactory; private bool sendToExchange = true; @@ -29,26 +35,41 @@ namespace PettingZoo.UI.Tab.Publisher private string routingKey = ""; private string queue = ""; private string replyTo = ""; - private bool replyToSpecified = true; + private bool replyToNewSubscriber; - private MessageType messageType; + private StoredPublisherMessage? selectedStoredMessage; + private StoredPublisherMessage? activeStoredMessage; + + private PublisherMessageType messageType; private UserControl? messageTypeControl; private ICommand? messageTypePublishCommand; + private RawPublisherViewModel? rawPublisherViewModel; private UserControl? rawPublisherView; + + private TapetiPublisherViewModel? tapetiPublisherViewModel; private UserControl? tapetiPublisherView; private readonly DelegateCommand publishCommand; + private readonly DelegateCommand saveCommand; + private readonly DelegateCommand saveAsCommand; + private readonly DelegateCommand deleteCommand; + private readonly DelegateCommand loadStoredMessageCommand; + private readonly DelegateCommand exportCommand; + private readonly DelegateCommand importCommand; + private readonly TabToolbarCommand[] toolbarCommands; private Window? tabHostWindow; + private bool disableCheckCanSave; public bool SendToExchange { get => sendToExchange; - set => SetField(ref sendToExchange, value, - delegateCommandsChanged: new [] { publishCommand }, - otherPropertiesChanged: new[] { nameof(SendToQueue), nameof(ExchangeVisibility), nameof(QueueVisibility), nameof(Title) }); + set => SetField(ref sendToExchange, value, + delegateCommandsChanged: new[] { publishCommand }, + otherPropertiesChanged: new[] + { nameof(SendToQueue), nameof(ExchangeVisibility), nameof(QueueVisibility), nameof(Title) }); } @@ -69,14 +90,16 @@ namespace PettingZoo.UI.Tab.Publisher public string RoutingKey { get => routingKey; - set => SetField(ref routingKey, value, delegateCommandsChanged: new[] { publishCommand }, otherPropertiesChanged: new[] { nameof(Title) }); + set => SetField(ref routingKey, value, delegateCommandsChanged: new[] { publishCommand }, + otherPropertiesChanged: new[] { nameof(Title) }); } public string Queue { get => queue; - set => SetField(ref queue, value, delegateCommandsChanged: new[] { publishCommand }, otherPropertiesChanged: new[] { nameof(Title) }); + set => SetField(ref queue, value, delegateCommandsChanged: new[] { publishCommand }, + otherPropertiesChanged: new[] { nameof(Title) }); } @@ -89,15 +112,16 @@ namespace PettingZoo.UI.Tab.Publisher public bool ReplyToSpecified { - get => replyToSpecified; - set => SetField(ref replyToSpecified, value, otherPropertiesChanged: new[] { nameof(ReplyToNewSubscriber) }); + get => !ReplyToNewSubscriber; + set => ReplyToNewSubscriber = !value; } public bool ReplyToNewSubscriber { - get => !ReplyToSpecified; - set => ReplyToSpecified = !value; + get => replyToNewSubscriber; + set => SetField(ref replyToNewSubscriber, value, + otherPropertiesChanged: new[] { nameof(ReplyToSpecified) }); } @@ -105,17 +129,17 @@ namespace PettingZoo.UI.Tab.Publisher public virtual Visibility QueueVisibility => SendToQueue ? Visibility.Visible : Visibility.Collapsed; - public MessageType MessageType + public PublisherMessageType MessageType { get => messageType; set { if (SetField(ref messageType, value, - otherPropertiesChanged: new[] - { - nameof(MessageTypeRaw), - nameof(MessageTypeTapeti) - })) + otherPropertiesChanged: new[] + { + nameof(MessageTypeRaw), + nameof(MessageTypeTapeti) + })) { SetMessageTypeControl(value); } @@ -124,14 +148,20 @@ namespace PettingZoo.UI.Tab.Publisher public bool MessageTypeRaw { - get => MessageType == MessageType.Raw; - set { if (value) MessageType = MessageType.Raw; } + get => MessageType == PublisherMessageType.Raw; + set + { + if (value) MessageType = PublisherMessageType.Raw; + } } public bool MessageTypeTapeti { - get => MessageType == MessageType.Tapeti; - set { if (value) MessageType = MessageType.Tapeti; } + get => MessageType == PublisherMessageType.Tapeti; + set + { + if (value) MessageType = PublisherMessageType.Tapeti; + } } @@ -142,12 +172,51 @@ namespace PettingZoo.UI.Tab.Publisher } + public ObservableCollectionEx StoredMessages => + storedPublisherMessagesViewModel.StoredMessages; + + public StoredPublisherMessage? SelectedStoredMessage + { + get => selectedStoredMessage; + set => SetField(ref selectedStoredMessage, value, delegateCommandsChanged: new[] { loadStoredMessageCommand, deleteCommand, exportCommand }); + } + + + public StoredPublisherMessage? ActiveStoredMessage + { + get => activeStoredMessage; + set => SetField(ref activeStoredMessage, value); + } + + public ICommand PublishCommand => publishCommand; + public ICommand SaveCommand => saveCommand; + public ICommand SaveAsCommand => saveAsCommand; + public ICommand DeleteCommand => deleteCommand; + public ICommand LoadStoredMessageCommand => loadStoredMessageCommand; + public ICommand ExportCommand => exportCommand; + public ICommand ImportCommand => importCommand; + + + private readonly DispatcherTimer publishedVisibilityTimer = new() + { + Interval = TimeSpan.FromSeconds(1) + }; + + private Visibility publishedVisibility = Visibility.Hidden; + public Visibility PublishedVisibility + { + get => publishedVisibility; + set => SetField(ref publishedVisibility, value); + } public string Title => SendToQueue - ? string.IsNullOrWhiteSpace(Queue) ? PublisherViewStrings.TabTitleEmpty : string.Format(PublisherViewStrings.TabTitle, Queue) - : string.IsNullOrWhiteSpace(RoutingKey) ? PublisherViewStrings.TabTitleEmpty : string.Format(PublisherViewStrings.TabTitle, RoutingKey); + ? string.IsNullOrWhiteSpace(Queue) ? PublisherViewStrings.TabTitleEmpty : + string.Format(PublisherViewStrings.TabTitle, Queue) + : string.IsNullOrWhiteSpace(RoutingKey) + ? PublisherViewStrings.TabTitleEmpty + : string.Format(PublisherViewStrings.TabTitle, RoutingKey); public IEnumerable ToolbarCommands => toolbarCommands; @@ -157,35 +226,83 @@ namespace PettingZoo.UI.Tab.Publisher string IPublishDestination.RoutingKey => SendToExchange ? RoutingKey : Queue; - public PublisherViewModel(ITabFactory tabFactory, IConnection connection, IExampleGenerator exampleGenerator, IPayloadMacroProcessor payloadMacroProcessor, ReceivedMessageInfo? fromReceivedMessage = null) + public PublisherViewModel(ITabFactory tabFactory, IConnection connection, IExampleGenerator exampleGenerator, + IPayloadMacroProcessor payloadMacroProcessor, + StoredPublisherMessagesViewModel storedPublisherMessagesViewModel, + ReceivedMessageInfo? fromReceivedMessage = null) { this.connection = connection; this.exampleGenerator = exampleGenerator; this.payloadMacroProcessor = payloadMacroProcessor; + this.storedPublisherMessagesViewModel = storedPublisherMessagesViewModel; this.tabFactory = tabFactory; publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute); + saveCommand = new DelegateCommand(SaveExecute, SaveCanExecute); + saveAsCommand = new DelegateCommand(SaveAsExecute); + deleteCommand = new DelegateCommand(DeleteExecute, SelectedMessageCanExecute); + loadStoredMessageCommand = new DelegateCommand(LoadStoredMessageExecute, SelectedMessageCanExecute); + exportCommand = new DelegateCommand(ExportExecute, SelectedMessageCanExecute); + importCommand = new DelegateCommand(ImportExecute); toolbarCommands = new[] { - new TabToolbarCommand(PublishCommand, PublisherViewStrings.CommandPublish, SvgIconHelper.LoadFromResource("/Images/PublishSend.svg")) + new TabToolbarCommand(PublishCommand, PublisherViewStrings.CommandPublish, + SvgIconHelper.LoadFromResource("/Images/PublishSend.svg")) }; if (fromReceivedMessage != null) SetMessageTypeControl(fromReceivedMessage); else - SetMessageTypeControl(MessageType.Raw); + SetMessageTypeControl(PublisherMessageType.Raw); + + + PropertyChanged += (_, _) => { saveCommand.RaiseCanExecuteChanged(); }; + + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse - null in design time + if (connection != null) + connection.StatusChanged += ConnectionStatusChanged; + + publishedVisibilityTimer.Tick += (_, _) => + { + PublishedVisibility = Visibility.Hidden; + publishedVisibilityTimer.Stop(); + }; } + public void Dispose() + { + connection.StatusChanged -= ConnectionStatusChanged; + GC.SuppressFinalize(this); + } + + + private void ConnectionStatusChanged(object? sender, StatusChangedEventArgs e) + { + Application.Current.Dispatcher.BeginInvoke(() => + { + publishCommand.RaiseCanExecuteChanged(); + }); + } + + private void PublishExecute() { messageTypePublishCommand?.Execute(null); + + PublishedVisibility = Visibility.Visible; + publishedVisibilityTimer.Stop(); + publishedVisibilityTimer.Start(); } private bool PublishCanExecute() { + if (connection.Status != ConnectionStatus.Connected) + return false; + if (SendToExchange) { if (string.IsNullOrWhiteSpace(Exchange) || string.IsNullOrWhiteSpace(RoutingKey)) @@ -201,13 +318,11 @@ namespace PettingZoo.UI.Tab.Publisher } - private void SetMessageTypeControl(MessageType value) + private void SetMessageTypeControl(PublisherMessageType value) { switch (value) { - case MessageType.Raw: - RawPublisherViewModel rawPublisherViewModel; - + case PublisherMessageType.Raw: if (rawPublisherView == null) { rawPublisherViewModel = new RawPublisherViewModel(connection, this, payloadMacroProcessor); @@ -216,19 +331,22 @@ namespace PettingZoo.UI.Tab.Publisher publishCommand.RaiseCanExecuteChanged(); }; - rawPublisherView ??= new RawPublisherView(rawPublisherViewModel); + // This is becoming a bit messy, find a cleaner way... + // TODO monitor header changes as well, instead of only the collection + rawPublisherViewModel.PropertyChanged += (_, _) => { saveCommand.RaiseCanExecuteChanged(); }; + rawPublisherViewModel.Headers.CollectionChanged += (_, _) => { saveCommand.RaiseCanExecuteChanged(); }; + + rawPublisherView = new RawPublisherView(rawPublisherViewModel); } else - rawPublisherViewModel = (RawPublisherViewModel)rawPublisherView.DataContext; + Debug.Assert(rawPublisherViewModel != null); MessageTypeControl = rawPublisherView; messageTypePublishCommand = rawPublisherViewModel.PublishCommand; break; - - case MessageType.Tapeti: - TapetiPublisherViewModel tapetiPublisherViewModel; + case PublisherMessageType.Tapeti: if (tapetiPublisherView == null) { tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, exampleGenerator, payloadMacroProcessor); @@ -237,19 +355,21 @@ namespace PettingZoo.UI.Tab.Publisher publishCommand.RaiseCanExecuteChanged(); }; - tapetiPublisherView ??= new TapetiPublisherView(tapetiPublisherViewModel); + tapetiPublisherViewModel.PropertyChanged += (_, _) => { saveCommand.RaiseCanExecuteChanged(); }; + + tapetiPublisherView = new TapetiPublisherView(tapetiPublisherViewModel); if (tabHostWindow != null) tapetiPublisherViewModel.HostWindowChanged(tabHostWindow); } else - tapetiPublisherViewModel = (TapetiPublisherViewModel)tapetiPublisherView.DataContext; + Debug.Assert(tapetiPublisherViewModel != null); MessageTypeControl = tapetiPublisherView; messageTypePublishCommand = tapetiPublisherViewModel.PublishCommand; break; - + default: throw new ArgumentException($@"Unknown message type: {value}", nameof(value)); } @@ -266,17 +386,17 @@ namespace PettingZoo.UI.Tab.Publisher if (TapetiPublisherViewModel.IsTapetiMessage(fromReceivedMessage)) { - var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, exampleGenerator, payloadMacroProcessor, fromReceivedMessage); + tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, exampleGenerator, payloadMacroProcessor, fromReceivedMessage); tapetiPublisherView = new TapetiPublisherView(tapetiPublisherViewModel); - MessageType = MessageType.Tapeti; + MessageType = PublisherMessageType.Tapeti; } else { - var rawPublisherViewModel = new RawPublisherViewModel(connection, this, payloadMacroProcessor, fromReceivedMessage); + rawPublisherViewModel = new RawPublisherViewModel(connection, this, payloadMacroProcessor, fromReceivedMessage); rawPublisherView = new RawPublisherView(rawPublisherViewModel); - MessageType = MessageType.Raw; + MessageType = PublisherMessageType.Raw; } } @@ -304,16 +424,234 @@ namespace PettingZoo.UI.Tab.Publisher (tapetiPublisherView?.DataContext as TapetiPublisherViewModel)?.HostWindowChanged(hostWindow); } + + + private bool SaveCanExecute() + { + if (disableCheckCanSave) + return false; + + return ActiveStoredMessage != null && !GetPublisherMessage().Equals(ActiveStoredMessage.Message); + } + + + private void SaveExecute() + { + if (ActiveStoredMessage == null) + return; + + storedPublisherMessagesViewModel.Save(ActiveStoredMessage, GetPublisherMessage(), message => + { + ActiveStoredMessage = message; + SelectedStoredMessage = message; + + saveCommand.RaiseCanExecuteChanged(); + }); + } + + + private void SaveAsExecute() + { + storedPublisherMessagesViewModel.SaveAs(GetPublisherMessage(), ActiveStoredMessage?.DisplayName, message => + { + ActiveStoredMessage = message; + SelectedStoredMessage = message; + }); + } + + + private void DeleteExecute() + { + if (SelectedStoredMessage == null) + return; + + var message = SelectedStoredMessage; + + storedPublisherMessagesViewModel.Delete(message, () => + { + if (SelectedStoredMessage == message) + SelectedStoredMessage = null; + + if (ActiveStoredMessage == message) + ActiveStoredMessage = null; + }); + } + + + private bool SelectedMessageCanExecute() + { + return SelectedStoredMessage != null; + } + + + private void LoadStoredMessageExecute() + { + if (SelectedStoredMessage == null) + return; + + var message = SelectedStoredMessage.Message; + + disableCheckCanSave = true; + try + { + MessageType = message.MessageType; + SendToExchange = message.SendToExchange; + Exchange = message.Exchange ?? ""; + RoutingKey = message.RoutingKey ?? ""; + Queue = message.Queue ?? ""; + ReplyToNewSubscriber = message.ReplyToNewSubscriber; + ReplyTo = message.ReplyTo ?? ""; + + // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault + switch (message.MessageType) + { + case PublisherMessageType.Raw: + if (message.RawPublisherMessage != null) + rawPublisherViewModel?.LoadPublisherMessage(message.RawPublisherMessage); + + break; + + case PublisherMessageType.Tapeti: + if (message.TapetiPublisherMessage != null) + tapetiPublisherViewModel?.LoadPublisherMessage(message.TapetiPublisherMessage); + + break; + } + + ActiveStoredMessage = SelectedStoredMessage; + } + finally + { + disableCheckCanSave = false; + saveCommand.RaiseCanExecuteChanged(); + } + } + + + private static readonly JsonSerializerSettings ExportImportSettings = new() + { + Converters = new List { new StringEnumConverter() } + }; + + + + private void ExportExecute() + { + if (SelectedStoredMessage == null) + return; + + var invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars())); + var invalidRegStr = string.Format(@"([{0}]*\.+$)|([{0}]+)", invalidChars); + var suggestedFilename = Regex.Replace(SelectedStoredMessage.DisplayName, invalidRegStr, "_"); + + var dialog = new SaveFileDialog + { + Filter = PublisherViewStrings.StoredMessagesExportImportFilter, + FileName = suggestedFilename + }; + + if (dialog.ShowDialog() != DialogResult.OK) + return; + + File.WriteAllText(dialog.FileName, JsonConvert.SerializeObject(SelectedStoredMessage.Message, ExportImportSettings), Encoding.UTF8); + } + + + private void ImportExecute() + { + var dialog = new OpenFileDialog + { + Filter = PublisherViewStrings.StoredMessagesExportImportFilter + }; + + if (dialog.ShowDialog() != DialogResult.OK) + return; + + var fileContents = File.ReadAllText(dialog.FileName, Encoding.UTF8); + var message = JsonConvert.DeserializeObject(fileContents, ExportImportSettings); + if (message == null) + return; + + var displayName = dialog.FileName.EndsWith(".pubmsg.json") + ? Path.GetFileName(dialog.FileName)[..^".pubmsg.json".Length] + : Path.GetFileNameWithoutExtension(dialog.FileName); + + storedPublisherMessagesViewModel.SaveAs(message, displayName, storedMessage => + { + SelectedStoredMessage = storedMessage; + }); + } + + + private PublisherMessage GetPublisherMessage() + { + return new PublisherMessage + { + MessageType = MessageType, + SendToExchange = SendToExchange, + Exchange = Exchange, + RoutingKey = RoutingKey, + Queue = Queue, + ReplyToNewSubscriber = ReplyToNewSubscriber, + ReplyTo = ReplyTo, + + RawPublisherMessage = MessageType == PublisherMessageType.Raw + ? rawPublisherViewModel?.GetPublisherMessage() + : null, + + TapetiPublisherMessage = MessageType == PublisherMessageType.Tapeti + ? tapetiPublisherViewModel?.GetPublisherMessage() + : null + }; + } } public class DesignTimePublisherViewModel : PublisherViewModel { - public DesignTimePublisherViewModel() : base(null!, null!, null!, null!) + public DesignTimePublisherViewModel() : base(null!, null!, null!, null!, new StoredPublisherMessagesViewModel(new DesignTimePublisherMessagesRepository())) { + StoredMessages.CollectionChanged += (_, _) => + { + if (StoredMessages.Count < 2) + return; + + SelectedStoredMessage = StoredMessages[0]; + ActiveStoredMessage = StoredMessages[1]; + }; + + PublishedVisibility = Visibility.Visible; } public override Visibility ExchangeVisibility => Visibility.Visible; public override Visibility QueueVisibility => Visibility.Visible; + + + private class DesignTimePublisherMessagesRepository : IPublisherMessagesRepository + { + public Task> GetStored() + { + return Task.FromResult(new StoredPublisherMessage[] + { + new(new Guid("16fdf930-2e4c-48f4-ae21-68dac9ca62e6"), "Design-time message 1", new PublisherMessage()), + new(new Guid("01d2671b-4426-4c1c-bcbc-61689d14796e"), "Design-time message 2", new PublisherMessage()) + } as IEnumerable); + } + + public Task Add(string displayName, PublisherMessage message) + { + throw new NotSupportedException(); + } + + public Task Update(Guid id, string displayName, PublisherMessage message) + { + throw new NotSupportedException(); + } + + public Task Delete(Guid id) + { + throw new NotSupportedException(); + } + } } } diff --git a/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.Designer.cs b/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.Designer.cs index 99ff390..0715fd3 100644 --- a/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.Designer.cs +++ b/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.Designer.cs @@ -168,6 +168,24 @@ namespace PettingZoo.UI.Tab.Publisher { } } + /// + /// Looks up a localized string similar to Saved messages. + /// + public static string PanelTitleMessages { + get { + return ResourceManager.GetString("PanelTitleMessages", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Message published. + /// + public static string Published { + get { + return ResourceManager.GetString("Published", resourceCulture); + } + } + /// /// Looks up a localized string similar to Re: . /// @@ -177,6 +195,15 @@ namespace PettingZoo.UI.Tab.Publisher { } } + /// + /// Looks up a localized string similar to PettingZoo message (*.pubmsg.json)|*.pubmsg.json. + /// + public static string StoredMessagesExportImportFilter { + get { + return ResourceManager.GetString("StoredMessagesExportImportFilter", resourceCulture); + } + } + /// /// Looks up a localized string similar to Publish: {0}. /// @@ -194,5 +221,50 @@ namespace PettingZoo.UI.Tab.Publisher { return ResourceManager.GetString("TabTitleEmpty", resourceCulture); } } + + /// + /// Looks up a localized string similar to Delete. + /// + public static string ToolbarDelete { + get { + return ResourceManager.GetString("ToolbarDelete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export.... + /// + public static string ToolbarExport { + get { + return ResourceManager.GetString("ToolbarExport", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Import.... + /// + public static string ToolbarImport { + get { + return ResourceManager.GetString("ToolbarImport", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save. + /// + public static string ToolbarSave { + get { + return ResourceManager.GetString("ToolbarSave", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save as.... + /// + public static string ToolbarSaveAs { + get { + return ResourceManager.GetString("ToolbarSaveAs", resourceCulture); + } + } } } diff --git a/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.resx b/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.resx index 9c10050..e881cd7 100644 --- a/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.resx +++ b/PettingZoo/UI/Tab/Publisher/PublisherViewStrings.resx @@ -153,13 +153,37 @@ Tapeti message + + Saved messages + + + Message published + Re: + + PettingZoo message (*.pubmsg.json)|*.pubmsg.json + Publish: {0} Publish + + Delete + + + Export... + + + Import... + + + Save + + + Save as... + \ No newline at end of file diff --git a/PettingZoo/UI/Tab/Publisher/RawPublisherView.xaml.cs b/PettingZoo/UI/Tab/Publisher/RawPublisherView.xaml.cs index bf5baf3..d07ddf1 100644 --- a/PettingZoo/UI/Tab/Publisher/RawPublisherView.xaml.cs +++ b/PettingZoo/UI/Tab/Publisher/RawPublisherView.xaml.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Windows; using System.Windows.Input; using System.Windows.Threading; -using PettingZoo.Core.Macros; namespace PettingZoo.UI.Tab.Publisher { diff --git a/PettingZoo/UI/Tab/Publisher/RawPublisherViewModel.cs b/PettingZoo/UI/Tab/Publisher/RawPublisherViewModel.cs index d36525a..f44d607 100644 --- a/PettingZoo/UI/Tab/Publisher/RawPublisherViewModel.cs +++ b/PettingZoo/UI/Tab/Publisher/RawPublisherViewModel.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -7,6 +6,7 @@ using System.Text; using System.Windows; using System.Windows.Input; using PettingZoo.Core.Connection; +using PettingZoo.Core.ExportImport.Publisher; using PettingZoo.Core.Macros; using PettingZoo.WPF.ViewModel; @@ -37,10 +37,17 @@ namespace PettingZoo.UI.Tab.Publisher + public MessageDeliveryMode DeliveryMode + { + get => deliveryMode; + set => SetField(ref deliveryMode, value, otherPropertiesChanged: new[] { nameof(DeliveryModeIndex) }); + } + + public int DeliveryModeIndex { - get => deliveryMode == MessageDeliveryMode.Persistent ? 1 : 0; - set => SetField(ref deliveryMode, value == 1 ? MessageDeliveryMode.Persistent : MessageDeliveryMode.NonPersistent); + get => DeliveryMode == MessageDeliveryMode.Persistent ? 1 : 0; + set => DeliveryMode = value == 1 ? MessageDeliveryMode.Persistent : MessageDeliveryMode.NonPersistent; } @@ -127,7 +134,7 @@ namespace PettingZoo.UI.Tab.Publisher } - public ObservableCollection
Headers { get; } = new(); + public ObservableCollectionEx
Headers { get; } = new(); public ICommand PublishCommand => publishCommand; @@ -194,6 +201,60 @@ namespace PettingZoo.UI.Tab.Publisher } + public RawPublisherMessage GetPublisherMessage() + { + return new RawPublisherMessage + { + DeliveryMode = DeliveryMode, + ContentType = ContentType, + CorrelationId = CorrelationId, + AppId = AppId, + ContentEncoding = ContentEncoding, + Expiration = Expiration, + MessageId = MessageId, + Priority = Priority, + Timestamp = Timestamp, + TypeProperty = TypeProperty, + UserId = UserId, + Payload = Payload, + EnableMacros = EnableMacros, + + Headers = Headers.Where(h => !h.IsEmpty()).ToDictionary(h => h.Key, h => h.Value) + }; + } + + + public void LoadPublisherMessage(RawPublisherMessage message) + { + DeliveryMode = message.DeliveryMode; + ContentType = message.ContentType ?? ""; + CorrelationId = message.CorrelationId ?? ""; + AppId = message.AppId ?? ""; + ContentEncoding = message.ContentEncoding ?? ""; + Expiration = message.Expiration ?? ""; + MessageId = message.MessageId ?? ""; + Priority = message.Priority ?? ""; + Timestamp = message.Timestamp ?? ""; + TypeProperty = message.TypeProperty ?? ""; + UserId = message.UserId ?? ""; + Payload = message.Payload ?? ""; + EnableMacros = message.EnableMacros; + + if (message.Headers != null) + { + Headers.ReplaceAll(message.Headers.Select(p => new Header + { + Key = p.Key, + Value = p.Value + })); + } + else + Headers.Clear(); + + AddHeader(); + } + + private static bool AnyNotEmpty(params string?[] values) { return values.Any(s => !string.IsNullOrEmpty(s)); diff --git a/PettingZoo/UI/Tab/Publisher/StoredPublisherMessagesStrings.Designer.cs b/PettingZoo/UI/Tab/Publisher/StoredPublisherMessagesStrings.Designer.cs new file mode 100644 index 0000000..468dd12 --- /dev/null +++ b/PettingZoo/UI/Tab/Publisher/StoredPublisherMessagesStrings.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PettingZoo.UI.Tab.Publisher { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class StoredPublisherMessagesStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal StoredPublisherMessagesStrings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Tab.Publisher.StoredPublisherMessagesStrings", typeof(StoredPublisherMessagesStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Do you want to delete the saved message '{0}'?. + /// + internal static string DeleteConfirmation { + get { + return ResourceManager.GetString("DeleteConfirmation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete message. + /// + internal static string DeleteConfirmationTitle { + get { + return ResourceManager.GetString("DeleteConfirmationTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save as.... + /// + internal static string DisplayNameDialogTitle { + get { + return ResourceManager.GetString("DisplayNameDialogTitle", resourceCulture); + } + } + } +} diff --git a/PettingZoo/UI/Tab/Publisher/StoredPublisherMessagesStrings.resx b/PettingZoo/UI/Tab/Publisher/StoredPublisherMessagesStrings.resx new file mode 100644 index 0000000..11e1170 --- /dev/null +++ b/PettingZoo/UI/Tab/Publisher/StoredPublisherMessagesStrings.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Do you want to delete the saved message '{0}'? + + + Delete message + + + Save as... + + \ No newline at end of file diff --git a/PettingZoo/UI/Tab/Publisher/StoredPublisherMessagesViewModel.cs b/PettingZoo/UI/Tab/Publisher/StoredPublisherMessagesViewModel.cs new file mode 100644 index 0000000..f8c6870 --- /dev/null +++ b/PettingZoo/UI/Tab/Publisher/StoredPublisherMessagesViewModel.cs @@ -0,0 +1,96 @@ +using System; +using System.Threading.Tasks; +using System.Windows; +using PettingZoo.Core.ExportImport.Publisher; +using PettingZoo.Core.Settings; +using PettingZoo.WPF.ViewModel; + +namespace PettingZoo.UI.Tab.Publisher +{ + public class StoredPublisherMessagesViewModel + { + private readonly IPublisherMessagesRepository publisherMessagesRepository; + + + public ObservableCollectionEx StoredMessages { get; } = new(); + + + public StoredPublisherMessagesViewModel(IPublisherMessagesRepository publisherMessagesRepository) + { + this.publisherMessagesRepository = publisherMessagesRepository; + + + Task.Run(async () => + { + var messages = await publisherMessagesRepository.GetStored(); + + await Application.Current.Dispatcher.BeginInvoke(() => + { + StoredMessages.ReplaceAll(messages); + }); + }); + } + + + public void Save(StoredPublisherMessage overwriteMessage, PublisherMessage message, Action onSaved) + { + Task.Run(async () => + { + var updatedMessage = await publisherMessagesRepository.Update(overwriteMessage.Id, overwriteMessage.DisplayName, message); + + await Application.Current.Dispatcher.BeginInvoke(() => + { + var index = StoredMessages.IndexOf(overwriteMessage); + if (index >= 0) + StoredMessages[index] = updatedMessage; + else + // Should not occur, but might as well handle it gracefully + StoredMessages.Add(updatedMessage); + + onSaved(updatedMessage); + }); + }); + } + + + public void SaveAs(PublisherMessage message, string? originalDisplayName, Action onSaved) + { + var displayName = originalDisplayName ?? ""; + if (!InputDialog.Execute(ref displayName, StoredPublisherMessagesStrings.DisplayNameDialogTitle)) + return; + + Task.Run(async () => + { + var storedMessage = await publisherMessagesRepository.Add(displayName, message); + + await Application.Current.Dispatcher.BeginInvoke(() => + { + StoredMessages.Add(storedMessage); + onSaved(storedMessage); + }); + }); + } + + + public void Delete(StoredPublisherMessage message, Action onDeleted) + { + if (MessageBox.Show( + string.Format(StoredPublisherMessagesStrings.DeleteConfirmation, message.DisplayName), + StoredPublisherMessagesStrings.DeleteConfirmationTitle, + MessageBoxButton.YesNo, + MessageBoxImage.Question) != MessageBoxResult.Yes) + return; + + Task.Run(async () => + { + await publisherMessagesRepository.Delete(message.Id); + + await Application.Current.Dispatcher.BeginInvoke(() => + { + StoredMessages.Remove(message); + onDeleted(); + }); + }); + } + } +} diff --git a/PettingZoo/UI/Tab/Publisher/TapetiPublisherView.xaml.cs b/PettingZoo/UI/Tab/Publisher/TapetiPublisherView.xaml.cs index db4468b..72e81aa 100644 --- a/PettingZoo/UI/Tab/Publisher/TapetiPublisherView.xaml.cs +++ b/PettingZoo/UI/Tab/Publisher/TapetiPublisherView.xaml.cs @@ -1,6 +1,5 @@ using System.Windows; using System.Windows.Controls; -using PettingZoo.Core.Macros; namespace PettingZoo.UI.Tab.Publisher { diff --git a/PettingZoo/UI/Tab/Publisher/TapetiPublisherViewModel.cs b/PettingZoo/UI/Tab/Publisher/TapetiPublisherViewModel.cs index b310820..6c2e1eb 100644 --- a/PettingZoo/UI/Tab/Publisher/TapetiPublisherViewModel.cs +++ b/PettingZoo/UI/Tab/Publisher/TapetiPublisherViewModel.cs @@ -3,6 +3,7 @@ using System.Text; using System.Windows; using System.Windows.Input; using PettingZoo.Core.Connection; +using PettingZoo.Core.ExportImport.Publisher; using PettingZoo.Core.Generator; using PettingZoo.Core.Macros; using PettingZoo.Core.Validation; @@ -132,6 +133,29 @@ namespace PettingZoo.UI.Tab.Publisher } + public TapetiPublisherMessage GetPublisherMessage() + { + return new TapetiPublisherMessage + { + CorrelationId = CorrelationId, + Payload = Payload, + EnableMacros = EnableMacros, + ClassName = ClassName, + AssemblyName = AssemblyName + }; + } + + + public void LoadPublisherMessage(TapetiPublisherMessage message) + { + CorrelationId = message.CorrelationId ?? ""; + Payload = message.Payload ?? ""; + EnableMacros = message.EnableMacros; + ClassName = message.ClassName ?? ""; + AssemblyName = message.AssemblyName ?? ""; + } + + private void BrowseClassExecute() { exampleGenerator.Select(tabHostWindow, example => diff --git a/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml b/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml index a6b3016..5d9d089 100644 --- a/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml +++ b/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml @@ -8,6 +8,7 @@ xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit" xmlns:connection="clr-namespace:PettingZoo.Core.Connection;assembly=PettingZoo.Core" + xmlns:controls="clr-namespace:PettingZoo.WPF.Controls;assembly=PettingZoo.WPF" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" @@ -36,8 +37,8 @@ - - + + @@ -50,6 +51,17 @@ + + + + + + + + + + + @@ -82,14 +94,14 @@ - + - + diff --git a/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml.cs b/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml.cs index f07a3b3..ef9c949 100644 --- a/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml.cs +++ b/PettingZoo/UI/Tab/Subscriber/SubscriberView.xaml.cs @@ -1,6 +1,4 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; +using System.Windows.Media; using ICSharpCode.AvalonEdit.Highlighting; namespace PettingZoo.UI.Tab.Subscriber @@ -35,17 +33,5 @@ namespace PettingZoo.UI.Tab.Subscriber if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) Background = Brushes.Transparent; } - - private void Toolbar_Loaded(object sender, RoutedEventArgs e) - { - // Hide arrow on the right side of the toolbar - var toolBar = sender as ToolBar; - - if (toolBar?.Template.FindName("OverflowGrid", toolBar) is FrameworkElement overflowGrid) - overflowGrid.Visibility = Visibility.Collapsed; - - if (toolBar?.Template.FindName("MainPanelBorder", toolBar) is FrameworkElement mainPanelBorder) - mainPanelBorder.Margin = new Thickness(0); - } } } diff --git a/PettingZoo/UI/Tab/Subscriber/SubscriberViewModel.cs b/PettingZoo/UI/Tab/Subscriber/SubscriberViewModel.cs index b2e7044..c82bb33 100644 --- a/PettingZoo/UI/Tab/Subscriber/SubscriberViewModel.cs +++ b/PettingZoo/UI/Tab/Subscriber/SubscriberViewModel.cs @@ -9,7 +9,7 @@ using System.Windows; using System.Windows.Forms; using System.Windows.Input; using PettingZoo.Core.Connection; -using PettingZoo.Core.ExportImport; +using PettingZoo.Core.ExportImport.Subscriber; using PettingZoo.Core.Rendering; using PettingZoo.WPF.ProgressWindow; using PettingZoo.WPF.ViewModel; @@ -25,7 +25,7 @@ namespace PettingZoo.UI.Tab.Subscriber { private readonly ILogger logger; private readonly ITabFactory tabFactory; - private readonly IConnection? connection; + private readonly IConnection connection; private readonly ISubscriber subscriber; private readonly IExportImportFormatProvider exportImportFormatProvider; private ReceivedMessageInfo? selectedMessage; @@ -106,8 +106,14 @@ namespace PettingZoo.UI.Tab.Subscriber public Visibility ReplyTabVisibility => IsReplyTab ? Visibility.Visible : Visibility.Collapsed; // ReSharper restore UnusedMember.Global + private readonly Guid subscribeConnectionId; + private ConnectionStatus connectionStatus = ConnectionStatus.Connecting; - public SubscriberViewModel(ILogger logger, ITabFactory tabFactory, IConnection? connection, ISubscriber subscriber, IExportImportFormatProvider exportImportFormatProvider, bool isReplyTab) + public Visibility StatusVisibility => connectionStatus is ConnectionStatus.Connecting or ConnectionStatus.Disconnected or ConnectionStatus.Error ? Visibility.Visible : Visibility.Collapsed; + public string StatusText => connectionStatus == ConnectionStatus.Connecting ? SubscriberViewStrings.StatusConnecting : SubscriberViewStrings.StatusDisconnected; + + + public SubscriberViewModel(ILogger logger, ITabFactory tabFactory, IConnection connection, ISubscriber subscriber, IExportImportFormatProvider exportImportFormatProvider, bool isReplyTab) { IsReplyTab = isReplyTab; @@ -133,6 +139,8 @@ namespace PettingZoo.UI.Tab.Subscriber createPublisherCommand = new DelegateCommand(CreatePublisherExecute, CreatePublisherCanExecute); + subscribeConnectionId = connection.ConnectionId; + connection.StatusChanged += ConnectionStatusChanged; subscriber.MessageReceived += SubscriberMessageReceived; subscriber.Start(); } @@ -141,11 +149,47 @@ namespace PettingZoo.UI.Tab.Subscriber public void Dispose() { GC.SuppressFinalize(this); + connection.StatusChanged -= ConnectionStatusChanged; newMessageTimer?.Dispose(); subscriber.Dispose(); } + private void ConnectionStatusChanged(object? sender, StatusChangedEventArgs e) + { + // If another connection has been made, this does not concern us + if (e.ConnectionId != subscribeConnectionId) + return; + + // The subscriber will not reconnect, so after the first disconnect it's over for us + if (connectionStatus is ConnectionStatus.Disconnected) + return; + + switch (e.Status) + { + case ConnectionStatus.Connecting: + case ConnectionStatus.Error: + return; + + case ConnectionStatus.Connected: + connectionStatus = ConnectionStatus.Connected; + break; + + case ConnectionStatus.Disconnected: + default: + connectionStatus = ConnectionStatus.Disconnected; + break; + } + + Application.Current.Dispatcher.BeginInvoke(() => + { + + RaisePropertyChanged(nameof(StatusVisibility)); + RaisePropertyChanged(nameof(StatusText)); + }); + } + + private void ClearExecute() { Messages.Clear(); @@ -254,7 +298,7 @@ namespace PettingZoo.UI.Tab.Subscriber private void CreatePublisherExecute() { - if (connection == null) + if (connection.Status != ConnectionStatus.Connected) return; tabFactory.CreatePublisherTab(connection, SelectedMessage); @@ -263,7 +307,7 @@ namespace PettingZoo.UI.Tab.Subscriber private bool CreatePublisherCanExecute() { - return connection != null && SelectedMessage != null; + return SelectedMessage != null; } diff --git a/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.Designer.cs b/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.Designer.cs index 84084b6..a84945c 100644 --- a/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.Designer.cs +++ b/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.Designer.cs @@ -194,5 +194,23 @@ namespace PettingZoo.UI.Tab.Subscriber { return ResourceManager.GetString("ReplyTabTitle", resourceCulture); } } + + /// + /// Looks up a localized string similar to Connecting.... + /// + public static string StatusConnecting { + get { + return ResourceManager.GetString("StatusConnecting", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Disconnected. + /// + public static string StatusDisconnected { + get { + return ResourceManager.GetString("StatusDisconnected", resourceCulture); + } + } } } diff --git a/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.resx b/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.resx index 1af6fd8..0fbe2dd 100644 --- a/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.resx +++ b/PettingZoo/UI/Tab/Subscriber/SubscriberViewStrings.resx @@ -162,4 +162,10 @@ Replies + + Connecting... + + + Disconnected + \ No newline at end of file diff --git a/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml b/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml index 2b9b461..867be1d 100644 --- a/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml +++ b/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml @@ -5,6 +5,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:undocked="clr-namespace:PettingZoo.UI.Tab.Undocked" xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" + xmlns:controls="clr-namespace:PettingZoo.WPF.Controls;assembly=PettingZoo.WPF" mc:Ignorable="d" d:DataContext="{d:DesignInstance undocked:DesignTimeUndockedTabHostViewModel, IsDesignTimeCreatable=True}" Title="{Binding Title}" @@ -12,7 +13,7 @@ Width="800" WindowStyle="ThreeDBorderWindow"> - + + diff --git a/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml.cs b/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml.cs index d6fcdcb..8484260 100644 --- a/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml.cs +++ b/PettingZoo/UI/Tab/Undocked/UndockedTabHostWindow.xaml.cs @@ -1,7 +1,4 @@ -using System.Windows; -using System.Windows.Controls; - -namespace PettingZoo.UI.Tab.Undocked +namespace PettingZoo.UI.Tab.Undocked { /// /// Interaction logic for UndockedTabHostWindow.xaml @@ -41,17 +38,5 @@ namespace PettingZoo.UI.Tab.Undocked viewModel.Deactivate(); }; } - - private void Toolbar_Loaded(object sender, RoutedEventArgs e) - { - // Hide arrow on the right side of the toolbar - var toolBar = sender as ToolBar; - - if (toolBar?.Template.FindName("OverflowGrid", toolBar) is FrameworkElement overflowGrid) - overflowGrid.Visibility = Visibility.Collapsed; - - if (toolBar?.Template.FindName("MainPanelBorder", toolBar) is FrameworkElement mainPanelBorder) - mainPanelBorder.Margin = new Thickness(0); - } } } diff --git a/PettingZoo/UI/Tab/ViewTabFactory.cs b/PettingZoo/UI/Tab/ViewTabFactory.cs index 2e118f8..f580aaa 100644 --- a/PettingZoo/UI/Tab/ViewTabFactory.cs +++ b/PettingZoo/UI/Tab/ViewTabFactory.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using PettingZoo.Core.Connection; -using PettingZoo.Core.ExportImport; +using PettingZoo.Core.ExportImport.Subscriber; using PettingZoo.Core.Generator; using PettingZoo.Core.Macros; using PettingZoo.UI.Tab.Publisher; @@ -17,6 +17,7 @@ namespace PettingZoo.UI.Tab private readonly IExampleGenerator exampleGenerator; private readonly IExportImportFormatProvider exportImportFormatProvider; private readonly IPayloadMacroProcessor payloadMacroProcessor; + private readonly StoredPublisherMessagesViewModel storedPublisherMessagesViewModel; // Not the cleanest way, but this factory itself can't be singleton without (justifyable) upsetting SimpleInjector private static ISubscriber? replySubscriber; @@ -24,17 +25,18 @@ namespace PettingZoo.UI.Tab public ViewTabFactory(ILogger logger, ITabHostProvider tabHostProvider, IExampleGenerator exampleGenerator, IExportImportFormatProvider exportImportFormatProvider, - IPayloadMacroProcessor payloadMacroProcessor) + IPayloadMacroProcessor payloadMacroProcessor, StoredPublisherMessagesViewModel storedPublisherMessagesViewModel) { this.logger = logger; this.tabHostProvider = tabHostProvider; this.exampleGenerator = exampleGenerator; this.exportImportFormatProvider = exportImportFormatProvider; this.payloadMacroProcessor = payloadMacroProcessor; + this.storedPublisherMessagesViewModel = storedPublisherMessagesViewModel; } - public void CreateSubscriberTab(IConnection? connection, ISubscriber subscriber) + public void CreateSubscriberTab(IConnection connection, ISubscriber subscriber) { InternalCreateSubscriberTab(connection, subscriber, false); } @@ -63,7 +65,7 @@ namespace PettingZoo.UI.Tab public void CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null) { - var viewModel = new PublisherViewModel(this, connection, exampleGenerator, payloadMacroProcessor, fromReceivedMessage); + var viewModel = new PublisherViewModel(this, connection, exampleGenerator, payloadMacroProcessor, storedPublisherMessagesViewModel, fromReceivedMessage); var tab = new ViewTab( new PublisherView(viewModel), viewModel, @@ -73,7 +75,7 @@ namespace PettingZoo.UI.Tab } - private ITab InternalCreateSubscriberTab(IConnection? connection, ISubscriber subscriber, bool isReplyTab) + private ITab InternalCreateSubscriberTab(IConnection connection, ISubscriber subscriber, bool isReplyTab) { var viewModel = new SubscriberViewModel(logger, this, connection, subscriber, exportImportFormatProvider, isReplyTab); var tab = new ViewTab( diff --git a/appveyor.yml b/appveyor.yml index 4d5365b..a0033ec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ image: Visual Studio 2022 install: - - choco install gitversion.portable -pre -y + - choco install gitversion.portable -pre -y --version 5.12.0 before_build: - nuget restore @@ -31,7 +31,7 @@ configuration: deploy: - provider: GitHub auth_token: - secure: dWOConKg3VTPvd9DmWOOKiX1SJCalaqKInuk9GlKQOZX2s+Bia49J7q+AHO8wFj7 + secure: l7meLHe4l/JZh+J0Gfd+Z+fMviSg3FDz4mIjp+S+PGdPgkpcc45t8Ae3EP2+dTGJkTvhSKNZB5Hmc76DOpfNKfrIQdBlQLDrQVS1nZRunjqL0klHVFRKqvwyH0DK5GK3 artifact: /PettingZoo-.*\.zip/ draft: false prerelease: false