From a934b425243de21bdcce21a10c16feb05eddb778 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 27 Jan 2023 14:50:20 -0300 Subject: [PATCH] Avoiding concurrent exceptions when looping through messages --- .../java/com/vitorpamplona/amethyst/model/User.kt | 10 +++++++++- .../amethyst/service/NostrChatRoomDataSource.kt | 8 ++++++-- .../service/NostrChatroomListDataSource.kt | 15 ++++++++------- .../amethyst/ui/navigation/Routes.kt | 2 +- .../amethyst/ui/screen/FeedViewModel.kt | 4 ++-- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index df2c447dd..e62e50df0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -86,7 +86,7 @@ class User(val pubkey: ByteArray) { @Synchronized fun getOrCreateChannel(user: User): MutableSet { return messages[user] ?: run { - val channel = mutableSetOf() + val channel = Collections.synchronizedSet(mutableSetOf()) messages[user] = channel channel } @@ -137,6 +137,14 @@ class User(val pubkey: ByteArray) { } } + fun hasSentMessagesTo(user: User?): Boolean { + val messagesToUser = messages[user] ?: return false + + return synchronized(messagesToUser) { + messagesToUser.firstOrNull { this == it.author } != null + } + } + // Model Observers private var listeners = setOf() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatRoomDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatRoomDataSource.kt index ed761f069..35a674226 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatRoomDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatRoomDataSource.kt @@ -33,9 +33,13 @@ object NostrChatRoomDataSource: NostrDataSource("ChatroomFeed") { // returns the last Note of each user. override fun feed(): List { - val messages = account.userProfile().messages[withUser]?.filter { account.isAcceptable(it) } + val messages = account.userProfile().messages[withUser] ?: return emptyList() - return messages?.sortedBy { it.event?.createdAt }?.reversed() ?: emptyList() + val filteredMessages = synchronized(messages) { + messages.filter { account.isAcceptable(it) } + } + + return filteredMessages.sortedBy { it.event?.createdAt }.reversed() } override fun updateChannelFilters() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt index 3e2dd8a14..ddf8536df 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt @@ -67,19 +67,20 @@ object NostrChatroomListDataSource: NostrDataSource("MailBoxFeed") { val messagingWith = messages.keys().toList().filter { account.isAcceptable(it) } val privateMessages = messagingWith.mapNotNull { - messages[it]?.sortedBy { it.event?.createdAt }?.lastOrNull { it.event != null } + val conversation = messages[it] + if (conversation != null) { + synchronized(conversation) { + conversation.sortedBy { it.event?.createdAt }.lastOrNull { it.event != null } + } + } else { + null + } } val publicChannels = account.followingChannels().map { it.notes.values.filter { account.isAcceptable(it) }.sortedBy { it.event?.createdAt }.lastOrNull { it.event != null } } - val channelsCreatedByMe = LocalCache.channels.values.filter { - it.creator == account.userProfile() - }.map { - it.notes.values.sortedBy { it.event?.createdAt }.lastOrNull { it.event != null } - } - return (privateMessages + publicChannels).filterNotNull().sortedBy { it.event?.createdAt }.reversed() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index 07a944de5..be0cd61dc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -138,7 +138,7 @@ private fun messagesHasNewItems(cache: NotificationCache): Boolean { return NostrChatroomListDataSource.feed().take(100).filter { // only for known sources val me = NostrChatroomListDataSource.account.userProfile() - it.channel != null || me.messages[it.author]?.firstOrNull { me == it.author } != null + it.channel != null || me.hasSentMessagesTo(it.author) }.filter { val lastTime = if (it.channel != null) { cache.load("Channel/${it.channel!!.idHex}", context) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index ef0855f21..f12bc2805 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -39,7 +39,7 @@ class NostrChatroomListKnownFeedViewModel: FeedViewModel(NostrChatroomListDataSo // Filter: all channels + PMs the account has replied to return super.newListFromDataSource().filter { val me = NostrChatroomListDataSource.account.userProfile() - it.channel != null || me.messages[it.author]?.firstOrNull { me == it.author } != null + it.channel != null || me.hasSentMessagesTo(it.author) } } } @@ -48,7 +48,7 @@ class NostrChatroomListNewFeedViewModel: FeedViewModel(NostrChatroomListDataSour // Filter: no channels + PMs the account has never replied to return super.newListFromDataSource().filter { val me = NostrChatroomListDataSource.account.userProfile() - it.channel == null && me.messages[it.author]?.firstOrNull { me == it.author } == null + it.channel == null && !me.hasSentMessagesTo(it.author) } } }