From c7562ca7ab4125f86c8bc4b1d6e884f14d378854 Mon Sep 17 00:00:00 2001 From: picolino Date: Tue, 7 Mar 2023 11:05:03 +0300 Subject: [PATCH 1/4] Add messages deduplication --- src/Chat.fs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Chat.fs b/src/Chat.fs index 3a14504..1fc655b 100644 --- a/src/Chat.fs +++ b/src/Chat.fs @@ -66,6 +66,7 @@ module Chat = AppSettingsFilePath: string UserSettingsFilePath: string ChatScrollViewOffset: float + MessagesListHashSet: Set } type Msg = @@ -248,6 +249,7 @@ module Chat = ConnectedUsers = [] MessageInput = "" MessagesList = [] + MessagesListHashSet = Set.empty SettingsVisible = false AppSettingsFilePath = appSettingsFilePath UserSettingsFilePath = userSettingsFilePath @@ -539,14 +541,17 @@ module Chat = logger.Debug $"[RemoteChatMessageReceived] Chat message {msg} package received by {client.RemoteEndPoint}" - match msg.RetranslationInfo.RetranslatedBy |> List.contains model.UserId with - | false -> - + let retranslatedByCurrentInstance = msg.RetranslationInfo.RetranslatedBy |> List.contains model.UserId + let receivedMessageAlreadyPresentsHere = model.MessagesListHashSet |> Set.contains (msg.GetHashCode()) + + if retranslatedByCurrentInstance || receivedMessageAlreadyPresentsHere + then + model, Cmd.none + else logger.Debug $"[RemoteChatMessageReceived] Chat message {msg} by {client.RemoteEndPoint} not being retranslated by this app. Retranslating..." - match msg.SecretCode = model.SecretCode with - | true -> - + if msg.SecretCode = model.SecretCode + then logger.Info $"[RemoteChatMessageReceived] Chat message {msg} for me by {client.RemoteEndPoint}. Append message to messages list..." let cmds = [ @@ -554,10 +559,8 @@ module Chat = Cmd.ofMsg <| RetranslateChatMessage msg ] model, Cmd.batch cmds - | false -> + else model, Cmd.ofMsg <| RetranslateChatMessage msg - | true -> - model, Cmd.none | RetranslateChatMessage msg -> let msg = { msg with RetranslationInfo = { msg.RetranslationInfo with RetranslatedBy = model.UserId :: msg.RetranslationInfo.RetranslatedBy } } model.Connections @@ -587,7 +590,13 @@ module Chat = | AppendLocalMessage m -> let msg = WaitThenSend (0, ScrollChatToEnd) // Delay 0 because rendering pipeline can't update vertical offset correctly without async message between frames let verticalOffset = Double.MaxValue // Vertical offset must be different between frames because of caching inside Avalonia.FuncUI library - { model with MessagesList = model.MessagesList @ [m]; ChatScrollViewOffset = verticalOffset }, Cmd.ofMsg msg + let model = { + model with + MessagesList = model.MessagesList @ [m] + MessagesListHashSet = model.MessagesListHashSet |> Set.add (m.GetHashCode()) + ChatScrollViewOffset = verticalOffset + } + model, Cmd.ofMsg msg | ScrollChatToEnd -> { model with ChatScrollViewOffset = Double.PositiveInfinity }, Cmd.none | TextChanged t -> From 27e03d10ac3ce2b73bf8e22411ae471250ef46e4 Mon Sep 17 00:00:00 2001 From: picolino Date: Tue, 7 Mar 2023 11:32:15 +0300 Subject: [PATCH 2/4] Override message hashing while deduplication --- src/Chat.fs | 4 ++-- src/Message.fs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Chat.fs b/src/Chat.fs index 1fc655b..70cbb2c 100644 --- a/src/Chat.fs +++ b/src/Chat.fs @@ -542,7 +542,7 @@ module Chat = logger.Debug $"[RemoteChatMessageReceived] Chat message {msg} package received by {client.RemoteEndPoint}" let retranslatedByCurrentInstance = msg.RetranslationInfo.RetranslatedBy |> List.contains model.UserId - let receivedMessageAlreadyPresentsHere = model.MessagesListHashSet |> Set.contains (msg.GetHashCode()) + let receivedMessageAlreadyPresentsHere = model.MessagesListHashSet |> Set.contains (msg.GetHalfHashCode()) if retranslatedByCurrentInstance || receivedMessageAlreadyPresentsHere then @@ -593,7 +593,7 @@ module Chat = let model = { model with MessagesList = model.MessagesList @ [m] - MessagesListHashSet = model.MessagesListHashSet |> Set.add (m.GetHashCode()) + MessagesListHashSet = model.MessagesListHashSet |> Set.add (m.Message.GetHalfHashCode()) ChatScrollViewOffset = verticalOffset } model, Cmd.ofMsg msg diff --git a/src/Message.fs b/src/Message.fs index a842e2b..b9e9b3a 100644 --- a/src/Message.fs +++ b/src/Message.fs @@ -14,12 +14,15 @@ module Message = RetranslationInfo: RetranslationInfo } - type ChatMessage = { + type ChatMessage = + { MessageText: string MessageSender: string DateTime: DateTime SecretCode: int UserId: string RetranslationInfo: RetranslationInfo - } + } + + member this.GetHalfHashCode() = hash <| this.MessageText + this.UserId + this.DateTime.Ticks.ToString() + this.SecretCode.ToString() From 6047bda74c3d4e7e26878bd3153470e4ae9e4ce8 Mon Sep 17 00:00:00 2001 From: picolino Date: Tue, 7 Mar 2023 11:35:46 +0300 Subject: [PATCH 3/4] Handle remote endpoint disposion exception --- src/Chat.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chat.fs b/src/Chat.fs index 70cbb2c..de19cbf 100644 --- a/src/Chat.fs +++ b/src/Chat.fs @@ -436,7 +436,7 @@ module Chat = | DisconnectClient socket -> let connections = model.Connections - |> List.where (fun o -> o.ConnectionId <> socket.RemoteEndPoint.ToString()) + |> List.where (fun o -> socket.RemoteEndPoint <> null && o.ConnectionId <> socket.RemoteEndPoint.ToString()) socket.Dispose() From 01afe1582461c35ede19b40ab54e94daaef1acd5 Mon Sep 17 00:00:00 2001 From: picolino Date: Tue, 7 Mar 2023 11:42:57 +0300 Subject: [PATCH 4/4] Close #2. Separate local topology and network topology --- src/Chat.fs | 69 ++++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/src/Chat.fs b/src/Chat.fs index de19cbf..11c8602 100644 --- a/src/Chat.fs +++ b/src/Chat.fs @@ -367,41 +367,44 @@ module Chat = model, Cmd.OfAsync.perform connectToLocalPeers () PeersConnected | StartConnectToRemotePeersLoop -> let connectToRemotePeers _ = async { - - logger.Debug $"[StartConnectToRemotePeersLoop] Defining non-connected known peers..." - - let nonConnectedRemotePeers = - model.KnownPeers - |> List.where (fun ep -> model.Connections |> List.exists (fun x -> x.ConnectionId = ep.ToString()) |> not) + if model.TcpListener.IsBound + then + logger.Debug $"[StartConnectToRemotePeersLoop] Defining non-connected known peers..." - if nonConnectedRemotePeers |> List.length > 0 - then logger.Debug $"[StartConnectToRemotePeersLoop] Non-connected known peers found {nonConnectedRemotePeers}. Connecting..." - else logger.Debug $"[StartConnectToRemotePeersLoop] All known peers already connected. Skipping..." - - return - nonConnectedRemotePeers - |> Array.ofList - |> Array.Parallel.map (fun ep -> - let socket = Tcp.client model.ClientPort + let nonConnectedRemotePeers = + model.KnownPeers + |> List.where (fun ep -> model.Connections |> List.exists (fun x -> x.ConnectionId = ep.ToString()) |> not) - logger.Debug $"[StartConnectToRemotePeersLoop] Connecting to remote tcp endpoint {ep} from {socket.LocalEndPoint}..." - - try - let connectedSocket = Tcp.connectSocket ep.Address ep.Port socket - let connectionEndpoint = { - ConnectionId = ep.ToString() - EndPoint = ep - Client = connectedSocket - } - Some connectionEndpoint - with - | e -> - logger.DebugException e $"[StartConnectToRemotePeersLoop] Connection to remote tcp endpoint {ep} failed" - socket.Dispose() - None - ) - |> Array.choose id - |> List.ofArray + if nonConnectedRemotePeers |> List.length > 0 + then logger.Debug $"[StartConnectToRemotePeersLoop] Non-connected known peers found {nonConnectedRemotePeers}. Connecting..." + else logger.Debug $"[StartConnectToRemotePeersLoop] All known peers already connected. Skipping..." + + return + nonConnectedRemotePeers + |> Array.ofList + |> Array.Parallel.map (fun ep -> + let socket = Tcp.client model.ClientPort + + logger.Debug $"[StartConnectToRemotePeersLoop] Connecting to remote tcp endpoint {ep} from {socket.LocalEndPoint}..." + + try + let connectedSocket = Tcp.connectSocket ep.Address ep.Port socket + let connectionEndpoint = { + ConnectionId = ep.ToString() + EndPoint = ep + Client = connectedSocket + } + Some connectionEndpoint + with + | e -> + logger.DebugException e $"[StartConnectToRemotePeersLoop] Connection to remote tcp endpoint {ep} failed" + socket.Dispose() + None + ) + |> Array.choose id + |> List.ofArray + else + return [] } model, Cmd.OfAsync.perform connectToRemotePeers () ConnectToRemotePeersIterationFinished