From a8a257a1a623130cbfb53fb6627319bd9dfd2eaf Mon Sep 17 00:00:00 2001 From: AL-Session <160798022+AL-Session@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:30:15 +1100 Subject: [PATCH] SES-1156 - Ban and delete functionality fix (#1428) * WIP * Investigation in progress * End of day push * WIP * Fixes #1416 * Cleanup * Added code to remove zombie messages caught in limbo during a ban & delete - still chock full o' debug while finding root cause * Root cause debug WIP * Push prior to cleanup * Cleaned up for PR * fix: mms delete, remove unnecessary values from sms * Addressed PR feedback * fix: fix unit tests * Added '.run' folder with test setup * Update README.md Test commit for CI * Re-added accidentally removed closing brace --------- Co-authored-by: alansley Co-authored-by: Al Lansley Co-authored-by: 0x330a <92654767+0x330a@users.noreply.github.com> --- .run/Run Tests.run.xml | 24 ++++++ README.md | 2 +- .../attachments/DatabaseAttachmentProvider.kt | 5 ++ .../components/ProfilePictureView.kt | 3 +- .../contacts/ContactSelectionListLoader.kt | 2 +- .../conversation/ConversationActionBarView.kt | 2 +- .../conversation/v2/ConversationActivityV2.kt | 77 ++++++++++++++---- .../v2/ConversationReactionOverlay.kt | 2 +- .../conversation/v2/ConversationViewModel.kt | 24 +++++- .../menus/ConversationActionModeCallback.kt | 2 +- .../v2/menus/ConversationMenuHelper.kt | 6 +- .../v2/messages/VisibleMessageView.kt | 6 +- .../securesms/database/Database.java | 5 +- .../ExpirationConfigurationDatabase.kt | 8 +- .../securesms/database/LokiMessageDatabase.kt | 8 +- .../securesms/database/MediaDatabase.java | 4 +- .../securesms/database/MmsDatabase.kt | 23 +++--- .../securesms/database/MmsSmsDatabase.java | 40 +++++++++- .../securesms/database/RecipientDatabase.java | 8 +- .../securesms/database/SearchDatabase.java | 4 +- .../securesms/database/SmsDatabase.java | 7 +- .../securesms/database/Storage.kt | 10 +-- .../securesms/database/ThreadDatabase.java | 16 ++-- .../home/ConversationOptionsBottomSheet.kt | 3 +- .../securesms/home/HomeActivity.kt | 2 +- .../securesms/home/PathActivity.kt | 1 + .../securesms/home/UserDetailsBottomSheet.kt | 4 +- .../MultipleRecipientNotificationBuilder.java | 4 +- .../SingleRecipientNotificationBuilder.java | 4 +- .../repository/ConversationRepository.kt | 78 ++++++++++++------- .../util/ConfigurationMessageUtilities.kt | 4 +- .../securesms/util/SharedConfigUtils.kt | 2 +- .../org/thoughtcrime/securesms/NoOpLogger.kt | 19 +++++ .../securesms/BaseViewModelTest.kt | 10 +++ .../DisappearingMessagesViewModelTest.kt | 2 +- .../v2/ConversationViewModelTest.kt | 14 +++- .../libsession/messaging/contacts/Contact.kt | 2 +- .../messaging/jobs/OpenGroupDeleteJob.kt | 3 +- .../messaging/messages/Destination.kt | 4 +- .../messaging/open_groups/OpenGroupApi.kt | 6 +- .../session/libsession/utilities/Address.kt | 14 ++-- .../libsession/utilities/GroupRecord.kt | 2 +- .../session/libsession/utilities/GroupUtil.kt | 18 ++--- .../utilities/recipients/Recipient.java | 8 +- 44 files changed, 350 insertions(+), 142 deletions(-) create mode 100644 .run/Run Tests.run.xml create mode 100644 app/src/sharedTest/java/org/thoughtcrime/securesms/NoOpLogger.kt diff --git a/.run/Run Tests.run.xml b/.run/Run Tests.run.xml new file mode 100644 index 0000000000..42b2e07744 --- /dev/null +++ b/.run/Run Tests.run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/README.md b/README.md index 723d50c758..17eaebf5fe 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Session Android +# Session Android [Download on the Google Play Store](https://getsession.org/android) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index 3fd91f7f63..20dc2bbade 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -184,16 +184,21 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) override fun deleteMessage(messageID: Long, isSms: Boolean) { val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase() else DatabaseComponent.get(context).mmsDatabase() + messagingDatabase.deleteMessage(messageID) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessage(messageID, isSms) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID, mms = !isSms) } override fun deleteMessages(messageIDs: List, threadId: Long, isSms: Boolean) { + val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase() else DatabaseComponent.get(context).mmsDatabase() + // Perform local delete messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId) + + // Perform online delete DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs, mms = !isSms) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index bb9cee027e..9a5eb730de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -132,7 +132,8 @@ class ProfilePictureView @JvmOverloads constructor( .diskCacheStrategy(DiskCacheStrategy.NONE) .circleCrop() .into(imageView) - } else if (recipient.isOpenGroupRecipient && recipient.groupAvatarId == null) { + } else if (recipient.isCommunityRecipient && recipient.groupAvatarId == null) { + glide.clear(imageView) glide.load(unknownOpenGroupDrawable) .centerCrop() .circleCrop() diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListLoader.kt index 3a2b2cbb5c..90e0ce50f2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListLoader.kt @@ -58,7 +58,7 @@ class ContactSelectionListLoader(context: Context, val mode: Int, val filter: St private fun getOpenGroups(contacts: List): List { return getItems(contacts, context.getString(R.string.fragment_contact_selection_open_groups_title)) { - it.address.isOpenGroup + it.address.isCommunity } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActionBarView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActionBarView.kt index 4bbcc3e021..184869b9ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActionBarView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActionBarView.kt @@ -116,7 +116,7 @@ class ConversationActionBarView @JvmOverloads constructor( ) } if (recipient.isGroupRecipient) { - val title = if (recipient.isOpenGroupRecipient) { + val title = if (recipient.isCommunityRecipient) { val userCount = openGroup?.let { lokiApiDb.getUserCount(it.room, it.server) } ?: 0 context.getString(R.string.ConversationActivity_active_member_count, userCount) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 5e6dd45c21..39c7bf4196 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -395,7 +395,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR)) val recipient = viewModel.recipient val openGroup = recipient.let { viewModel.openGroup } - if (recipient == null || (recipient.isOpenGroupRecipient && openGroup == null)) { + if (recipient == null || (recipient.isCommunityRecipient && openGroup == null)) { Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show() return finish() } @@ -976,11 +976,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe view.glide = glide view.onCandidateSelected = { handleMentionSelected(it) } additionalContentContainer.addView(view) - val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, recipient.isOpenGroupRecipient) + val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, recipient.isCommunityRecipient) this.mentionCandidatesView = view view.show(candidates, viewModel.threadId) } else { - val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, recipient.isOpenGroupRecipient) + val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, recipient.isCommunityRecipient) this.mentionCandidatesView!!.setMentionCandidates(candidates) } isShowingMentionCandidatesView = true @@ -1197,7 +1197,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun copyOpenGroupUrl(thread: Recipient) { - if (!thread.isOpenGroupRecipient) { return } + if (!thread.isCommunityRecipient) { return } val threadId = threadDb.getThreadIdIfExistsFor(thread) ?: return val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return @@ -1361,7 +1361,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } else originalMessage.individualRecipient.address // Send it reactionMessage.reaction = Reaction.from(originalMessage.timestamp, originalAuthor.serialize(), emoji, true) - if (recipient.isOpenGroupRecipient) { + if (recipient.isCommunityRecipient) { val messageServerId = lokiMessageDb.getServerID(originalMessage.id, !originalMessage.isMms) ?: return viewModel.openGroup?.let { OpenGroupApi.addReaction(it.room, it.server, messageServerId, emoji) @@ -1385,7 +1385,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } else originalMessage.individualRecipient.address message.reaction = Reaction.from(originalMessage.timestamp, originalAuthor.serialize(), emoji, false) - if (recipient.isOpenGroupRecipient) { + if (recipient.isCommunityRecipient) { val messageServerId = lokiMessageDb.getServerID(originalMessage.id, !originalMessage.isMms) ?: return viewModel.openGroup?.let { OpenGroupApi.deleteReaction(it.room, it.server, messageServerId, emoji) @@ -1782,7 +1782,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe sendAttachments(slideDeck.asAttachments(), body) } INVITE_CONTACTS -> { - if (viewModel.recipient?.isOpenGroupRecipient != true) { return } + if (viewModel.recipient?.isCommunityRecipient != true) { return } val extras = intent?.extras ?: return if (!intent.hasExtra(selectedContactsKey)) { return } val selectedContacts = extras.getStringArray(selectedContactsKey)!! @@ -1848,19 +1848,62 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe handleLongPress(messages.first(), 0) //TODO: begin selection mode } + // The option to "Delete just for me" or "Delete for everyone" + private fun showDeleteOrDeleteForEveryoneInCommunityUI(messages: Set) { + val bottomSheet = DeleteOptionsBottomSheet() + bottomSheet.recipient = viewModel.recipient!! + bottomSheet.onDeleteForMeTapped = { + messages.forEach(viewModel::deleteLocally) + bottomSheet.dismiss() + endActionMode() + } + bottomSheet.onDeleteForEveryoneTapped = { + messages.forEach(viewModel::deleteForEveryone) + bottomSheet.dismiss() + endActionMode() + } + bottomSheet.onCancelTapped = { + bottomSheet.dismiss() + endActionMode() + } + bottomSheet.show(supportFragmentManager, bottomSheet.tag) + } + + private fun showDeleteLocallyUI(messages: Set) { + val messageCount = 1 + showSessionDialog { + title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) + text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) + button(R.string.delete) { messages.forEach(viewModel::deleteLocally); endActionMode() } + cancelButton(::endActionMode) + } + } + + // Note: The messages in the provided set may be a single message, or multiple if there are a + // group of selected messages. override fun deleteMessages(messages: Set) { - val recipient = viewModel.recipient ?: return + val recipient = viewModel.recipient + if (recipient == null) { + Log.w("ConversationActivityV2", "Asked to delete messages but could not obtain viewModel recipient - aborting.") + return + } + val allSentByCurrentUser = messages.all { it.isOutgoing } val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id, it.isMms) != null } - if (recipient.isOpenGroupRecipient) { - val messageCount = 1 + + // If the recipient is a community then we delete the message for everyone + if (recipient.isCommunityRecipient) { + val messageCount = 1 // Only used for plurals string showSessionDialog { title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) - button(R.string.delete) { messages.forEach(viewModel::deleteForEveryone); endActionMode() } + button(R.string.delete) { + messages.forEach(viewModel::deleteForEveryone); endActionMode() + } cancelButton { endActionMode() } } + // Otherwise if this is a 1-on-1 conversation we may decided to delete just for ourselves or delete for everyone } else if (allSentByCurrentUser && allHasHash) { val bottomSheet = DeleteOptionsBottomSheet() bottomSheet.recipient = recipient @@ -1879,13 +1922,17 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe endActionMode() } bottomSheet.show(supportFragmentManager, bottomSheet.tag) - } else { + } + else // Finally, if this is a closed group and you are deleting someone else's message(s) + // then we can only delete locally. + { val messageCount = 1 - showSessionDialog { title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) - button(R.string.delete) { messages.forEach(viewModel::deleteLocally); endActionMode() } + button(R.string.delete) { + messages.forEach(viewModel::deleteLocally); endActionMode() + } cancelButton(::endActionMode) } } @@ -1904,7 +1951,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe showSessionDialog { title(R.string.ConversationFragment_ban_selected_user) text("This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there.") - button(R.string.ban) { viewModel.banAndDeleteAll(messages.first().individualRecipient); endActionMode() } + button(R.string.ban) { viewModel.banAndDeleteAll(messages.first()); endActionMode() } cancelButton(::endActionMode) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt index 405d2a3c0e..d48d761f94 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt @@ -541,7 +541,7 @@ class ConversationReactionOverlay : FrameLayout { items += ActionItem(R.attr.menu_copy_icon, R.string.copy, { handleActionItemClicked(Action.COPY_MESSAGE) }) } // Copy Session ID - if (recipient.isGroupRecipient && !recipient.isOpenGroupRecipient && message.recipient.address.toString() != userPublicKey) { + if (recipient.isGroupRecipient && !recipient.isCommunityRecipient && message.recipient.address.toString() != userPublicKey) { items += ActionItem(R.attr.menu_copy_icon, R.string.activity_conversation_menu_copy_session_id, { handleActionItemClicked(Action.COPY_SESSION_ID) }) } // Delete message diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 677dd22f60..80d6df87fe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -1,18 +1,23 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Context + import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope + import com.goterl.lazysodium.utils.KeyPair + import dagger.assisted.Assisted import dagger.assisted.AssistedInject + import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch + import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupApi @@ -22,9 +27,12 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.database.MmsSmsDatabase + import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.repository.ConversationRepository + import java.util.UUID class ConversationViewModel( @@ -144,9 +152,14 @@ class ConversationViewModel( } fun deleteForEveryone(message: MessageRecord) = viewModelScope.launch { - val recipient = recipient ?: return@launch + val recipient = recipient ?: return@launch Log.w("Loki", "Recipient was null for delete for everyone - aborting delete operation.") + repository.deleteForEveryone(threadId, recipient, message) + .onSuccess { + Log.d("Loki", "Deleted message ${message.id} ") + } .onFailure { + Log.w("Loki", "FAILED TO delete message ${message.id} ") showMessage("Couldn't delete message due to error: $it") } } @@ -168,10 +181,15 @@ class ConversationViewModel( } } - fun banAndDeleteAll(recipient: Recipient) = viewModelScope.launch { - repository.banAndDeleteAll(threadId, recipient) + fun banAndDeleteAll(messageRecord: MessageRecord) = viewModelScope.launch { + + repository.banAndDeleteAll(threadId, messageRecord.individualRecipient) .onSuccess { + // At this point the server side messages have been successfully deleted.. showMessage("Successfully banned user and deleted all their messages") + + // ..so we can now delete all their messages in this thread from local storage & remove the views. + repository.deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord) } .onFailure { showMessage("Couldn't execute request due to error: $it") diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index 3746aa52e4..2788d35dd2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -65,7 +65,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText // Copy Session ID menu.findItem(R.id.menu_context_copy_public_key).isVisible = - (thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey) + (thread.isGroupRecipient && !thread.isCommunityRecipient && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey) // Message detail menu.findItem(R.id.menu_message_details).isVisible = selectedItems.size == 1 // Resend diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt index dadf138ead..11069937a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt @@ -50,7 +50,7 @@ object ConversationMenuHelper { ) { // Prepare menu.clear() - val isOpenGroup = thread.isOpenGroupRecipient + val isOpenGroup = thread.isCommunityRecipient // Base menu (options that should always be present) inflater.inflate(R.menu.menu_conversation, menu) // Expiring messages @@ -253,7 +253,7 @@ object ConversationMenuHelper { } private fun copyOpenGroupUrl(context: Context, thread: Recipient) { - if (!thread.isOpenGroupRecipient) { return } + if (!thread.isCommunityRecipient) { return } val listener = context as? ConversationMenuListener ?: return listener.copyOpenGroupUrl(thread) } @@ -300,7 +300,7 @@ object ConversationMenuHelper { } private fun inviteContacts(context: Context, thread: Recipient) { - if (!thread.isOpenGroupRecipient) { return } + if (!thread.isCommunityRecipient) { return } val intent = Intent(context, SelectContactsActivity::class.java) val activity = context as AppCompatActivity activity.startActivityForResult(intent, ConversationActivityV2.INVITE_CONTACTS) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 9dc3d2ab2d..a59a102d1c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -166,7 +166,7 @@ class VisibleMessageView : LinearLayout { binding.profilePictureView.publicKey = senderSessionID binding.profilePictureView.update(message.individualRecipient) binding.profilePictureView.setOnClickListener { - if (thread.isOpenGroupRecipient) { + if (thread.isCommunityRecipient) { val openGroup = lokiThreadDb.getOpenGroupChat(threadID) if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED && openGroup?.canWrite == true) { // TODO: support v2 soon @@ -179,7 +179,7 @@ class VisibleMessageView : LinearLayout { maybeShowUserDetails(senderSessionID, threadID) } } - if (thread.isOpenGroupRecipient) { + if (thread.isCommunityRecipient) { val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return var standardPublicKey = "" var blindedPublicKey: String? = null @@ -195,7 +195,7 @@ class VisibleMessageView : LinearLayout { } binding.senderNameTextView.isVisible = !message.isOutgoing && (isStartOfMessageCluster && (isGroupThread || snIsSelected)) val contactContext = - if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR + if (thread.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID // Unread marker diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java index b6b224589e..e1879d5230 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java @@ -26,6 +26,7 @@ import androidx.annotation.NonNull; import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.WindowDebouncer; +import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; @@ -77,11 +78,11 @@ public abstract class Database { notifyConversationListListeners(); } - protected void setNotifyConverationListeners(Cursor cursor, long threadId) { + protected void setNotifyConversationListeners(Cursor cursor, long threadId) { cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForThread(threadId)); } - protected void setNotifyConverationListListeners(Cursor cursor) { + protected void setNotifyConversationListListeners(Cursor cursor) { cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.ConversationList.CONTENT_URI); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ExpirationConfigurationDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ExpirationConfigurationDatabase.kt index a65c22545b..013bbf5cb5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ExpirationConfigurationDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ExpirationConfigurationDatabase.kt @@ -6,8 +6,8 @@ import android.database.Cursor import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.ExpirationDatabaseMetadata import org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX -import org.session.libsession.utilities.GroupUtil.OPEN_GROUP_INBOX_PREFIX -import org.session.libsession.utilities.GroupUtil.OPEN_GROUP_PREFIX +import org.session.libsession.utilities.GroupUtil.COMMUNITY_INBOX_PREFIX +import org.session.libsession.utilities.GroupUtil.COMMUNITY_PREFIX import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper class ExpirationConfigurationDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) { @@ -38,8 +38,8 @@ class ExpirationConfigurationDatabase(context: Context, helper: SQLCipherOpenHel INSERT INTO $TABLE_NAME ($THREAD_ID) SELECT ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ID} FROM ${ThreadDatabase.TABLE_NAME}, ${RecipientDatabase.TABLE_NAME} WHERE ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$CLOSED_GROUP_PREFIX%' - AND ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$OPEN_GROUP_PREFIX%' - AND ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$OPEN_GROUP_INBOX_PREFIX%' + AND ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$COMMUNITY_PREFIX%' + AND ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$COMMUNITY_INBOX_PREFIX%' AND EXISTS (SELECT ${RecipientDatabase.EXPIRE_MESSAGES} FROM ${RecipientDatabase.TABLE_NAME} WHERE ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} = ${RecipientDatabase.TABLE_NAME}.${RecipientDatabase.ADDRESS} AND ${RecipientDatabase.EXPIRE_MESSAGES} > 0) """.trimIndent() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt index 441c979108..18dd42818d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt @@ -4,6 +4,7 @@ import android.content.ContentValues import android.content.Context import net.zetetic.database.sqlcipher.SQLiteDatabase.CONFLICT_REPLACE import org.session.libsignal.database.LokiMessageDatabaseProtocol +import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol { @@ -72,7 +73,12 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab "${Companion.messageID} = ? AND $messageType = ?", arrayOf(messageID.toString(), (if (isSms) SMS_TYPE else MMS_TYPE).toString())) { cursor -> cursor.getInt(serverID).toLong() - } ?: return + } + + if (serverID == null) { + Log.w(this::class.simpleName, "Could not get server ID to delete message with ID: $messageID") + return + } database.beginTransaction() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java index 1b273de929..63db0c66ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -68,7 +68,7 @@ public class MediaDatabase extends Database { public Cursor getGalleryMediaForThread(long threadId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); Cursor cursor = database.rawQuery(GALLERY_MEDIA_QUERY, new String[]{threadId+""}); - setNotifyConverationListeners(cursor, threadId); + setNotifyConversationListeners(cursor, threadId); return cursor; } @@ -83,7 +83,7 @@ public class MediaDatabase extends Database { public Cursor getDocumentMediaForThread(long threadId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); Cursor cursor = database.rawQuery(DOCUMENT_MEDIA_QUERY, new String[]{threadId+""}); - setNotifyConverationListeners(cursor, threadId); + setNotifyConversationListeners(cursor, threadId); return cursor; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 62db50a1ba..15a515ee5b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -19,9 +19,9 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context import android.database.Cursor -import android.provider.ContactsContract.CommonDataKinds.BaseTypes import com.annimon.stream.Stream import com.google.android.mms.pdu_alt.PduHeaders +import org.apache.commons.lang3.StringUtils import org.json.JSONArray import org.json.JSONException import org.json.JSONObject @@ -214,7 +214,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa fun getMessage(messageId: Long): Cursor { val cursor = rawQuery(RAW_ID_WHERE, arrayOf(messageId.toString())) - setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId)) + setNotifyConversationListeners(cursor, getThreadIdForMessage(messageId)) return cursor } @@ -859,8 +859,10 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa */ private fun deleteMessages(messageIds: Array) { if (messageIds.isEmpty()) { + Log.w(TAG, "No message Ids provided to MmsDatabase.deleteMessages - aborting delete operation!") return } + // don't need thread IDs val queryBuilder = StringBuilder() for (i in messageIds.indices) { @@ -883,6 +885,8 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa notifyStickerPackListeners() } + // Caution: The bool returned from `deleteMessage` is NOT "Was the message successfully deleted?" + // - it is "Was the thread deleted because removing that message resulted in an empty thread"! override fun deleteMessage(messageId: Long): Boolean { val threadId = getThreadIdForMessage(messageId) val attachmentDatabase = get(context).attachmentDatabase() @@ -899,14 +903,15 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa } override fun deleteMessages(messageIds: LongArray, threadId: Long): Boolean { - val attachmentDatabase = get(context).attachmentDatabase() - val groupReceiptDatabase = get(context).groupReceiptDatabase() + val argsArray = messageIds.map { "?" } + val argValues = messageIds.map { it.toString() }.toTypedArray() - queue(Runnable { attachmentDatabase.deleteAttachmentsForMessages(messageIds) }) - groupReceiptDatabase.deleteRowsForMessages(messageIds) - - val database = databaseHelper.writableDatabase - database!!.delete(TABLE_NAME, ID_IN, arrayOf(messageIds.joinToString(","))) + val db = databaseHelper.writableDatabase + db.delete( + TABLE_NAME, + ID + " IN (" + StringUtils.join(argsArray, ',') + ")", + argValues + ) val threadDeleted = get(context).threadDatabase().update(threadId, false, true) notifyConversationListeners(threadId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 63a9ec5c29..98f118d59d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -183,7 +183,7 @@ public class MmsSmsDatabase extends Database { String limitStr = limit > 0 || offset > 0 ? offset + ", " + limit : null; Cursor cursor = queryTables(PROJECTION, selection, order, limitStr); - setNotifyConverationListeners(cursor, threadId); + setNotifyConversationListeners(cursor, threadId); return cursor; } @@ -209,6 +209,44 @@ public class MmsSmsDatabase extends Database { } } + // Builds up and returns a list of all all the messages sent by this user in the given thread. + // Used to do a pass through our local database to remove records when a user has "Ban & Delete" + // called on them in a Community. + public Set getAllMessageRecordsFromSenderInThread(long threadId, String serializedAuthor) { + String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.ADDRESS + " = \"" + serializedAuthor + "\""; + Set identifiedMessages = new HashSet(); + + // Try everything with resources so that they auto-close on end of scope + try (Cursor cursor = queryTables(PROJECTION, selection, null, null)) { + try (MmsSmsDatabase.Reader reader = readerFor(cursor)) { + MessageRecord messageRecord; + while ((messageRecord = reader.getNext()) != null) { + identifiedMessages.add(messageRecord); + } + } + } + return identifiedMessages; + } + + // Version of the above `getAllMessageRecordsFromSenderInThread` method that returns the message + // Ids rather than the set of MessageRecords - currently unused by potentially useful in the future. + public Set getAllMessageIdsFromSenderInThread(long threadId, String serializedAuthor) { + String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.ADDRESS + " = \"" + serializedAuthor + "\""; + + Set identifiedMessages = new HashSet(); + + // Try everything with resources so that they auto-close on end of scope + try (Cursor cursor = queryTables(PROJECTION, selection, null, null)) { + try (MmsSmsDatabase.Reader reader = readerFor(cursor)) { + MessageRecord messageRecord; + while ((messageRecord = reader.getNext()) != null) { + identifiedMessages.add(messageRecord.id); + } + } + } + return identifiedMessages; + } + public long getLastSentMessageFromSender(long threadId, String serializedAuthor) { String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 69c9fa87f1..8dbef32017 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -1,6 +1,6 @@ package org.thoughtcrime.securesms.database; -import static org.session.libsession.utilities.GroupUtil.OPEN_GROUP_PREFIX; +import static org.session.libsession.utilities.GroupUtil.COMMUNITY_PREFIX; import android.content.ContentValues; import android.content.Context; @@ -123,18 +123,18 @@ public class RecipientDatabase extends Database { public static String getUpdateApprovedCommand() { return "UPDATE "+ TABLE_NAME + " " + "SET " + APPROVED + " = 1, " + APPROVED_ME + " = 1 " + - "WHERE " + ADDRESS + " NOT LIKE '" + OPEN_GROUP_PREFIX + "%'"; + "WHERE " + ADDRESS + " NOT LIKE '" + COMMUNITY_PREFIX + "%'"; } public static String getUpdateResetApprovedCommand() { return "UPDATE "+ TABLE_NAME + " " + "SET " + APPROVED + " = 0, " + APPROVED_ME + " = 0 " + - "WHERE " + ADDRESS + " NOT LIKE '" + OPEN_GROUP_PREFIX + "%'"; + "WHERE " + ADDRESS + " NOT LIKE '" + COMMUNITY_PREFIX + "%'"; } public static String getUpdateApprovedSelectConversations() { return "UPDATE "+ TABLE_NAME + " SET "+APPROVED+" = 1, "+APPROVED_ME+" = 1 "+ - "WHERE "+ADDRESS+ " NOT LIKE '"+OPEN_GROUP_PREFIX+"%' " + + "WHERE "+ADDRESS+ " NOT LIKE '"+ COMMUNITY_PREFIX +"%' " + "AND ("+ADDRESS+" IN (SELECT "+ThreadDatabase.TABLE_NAME+"."+ThreadDatabase.ADDRESS+" FROM "+ThreadDatabase.TABLE_NAME+" WHERE ("+ThreadDatabase.MESSAGE_COUNT+" != 0) "+ "OR "+ADDRESS+" IN (SELECT "+GroupDatabase.TABLE_NAME+"."+GroupDatabase.ADMINS+" FROM "+GroupDatabase.TABLE_NAME+")))"; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java index eac6a5fbc3..0782049478 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java @@ -119,7 +119,7 @@ public class SearchDatabase extends Database { int queryLimit = Math.min(query.length()*50,500); Cursor cursor = db.rawQuery(MESSAGES_QUERY, new String[] { prefixQuery, prefixQuery, String.valueOf(queryLimit) }); - setNotifyConverationListListeners(cursor); + setNotifyConversationListListeners(cursor); return cursor; } @@ -128,7 +128,7 @@ public class SearchDatabase extends Database { String prefixQuery = adjustQuery(query); Cursor cursor = db.rawQuery(MESSAGES_FOR_THREAD_QUERY, new String[] { prefixQuery, String.valueOf(threadId), prefixQuery, String.valueOf(threadId) }); - setNotifyConverationListListeners(cursor); + setNotifyConversationListListeners(cursor); return cursor; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 2c0c33dda8..61f17a2d86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -621,10 +621,12 @@ public class SmsDatabase extends MessagingDatabase { public Cursor getMessageCursor(long messageId) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId + ""}, null, null, null); - setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId)); + setNotifyConversationListeners(cursor, getThreadIdForMessage(messageId)); return cursor; } + // Caution: The bool returned from `deleteMessage` is NOT "Was the message successfully deleted?" + // - it is "Was the thread deleted because removing that message resulted in an empty thread"! @Override public boolean deleteMessage(long messageId) { Log.i("MessageDatabase", "Deleting: " + messageId); @@ -645,9 +647,6 @@ public class SmsDatabase extends MessagingDatabase { argValues[i] = (messageIds[i] + ""); } - String combinedMessageIdArgss = StringUtils.join(messageIds, ','); - String combinedMessageIds = StringUtils.join(messageIds, ','); - Log.i("MessageDatabase", "Deleting: " + combinedMessageIds); SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.delete( TABLE_NAME, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 584394a86c..ae0569e42a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -121,7 +121,7 @@ open class Storage( ) volatile.set(newVolatileParams) } - } else if (address.isOpenGroup) { + } else if (address.isCommunity) { // these should be added on the group join / group info fetch Log.w("Loki", "Thread created called for open group address, not adding any extra information") } @@ -152,7 +152,7 @@ open class Storage( val sessionId = GroupUtil.doubleDecodeGroupId(address.serialize()) volatile.eraseLegacyClosedGroup(sessionId) groups.eraseLegacyGroup(sessionId) - } else if (address.isOpenGroup) { + } else if (address.isCommunity) { // these should be removed in the group leave / handling new configs Log.w("Loki", "Thread delete called for open group address, expecting to be handled elsewhere") } @@ -257,7 +257,7 @@ open class Storage( // recipient closed group recipient.isClosedGroupRecipient -> config.getOrConstructLegacyGroup(GroupUtil.doubleDecodeGroupId(recipient.address.serialize())) // recipient is open group - recipient.isOpenGroupRecipient -> { + recipient.isCommunityRecipient -> { val openGroupJoinUrl = getOpenGroup(threadId)?.joinURL ?: return BaseCommunityInfo.parseFullUrl(openGroupJoinUrl)?.let { (base, room, pubKey) -> config.getOrConstructCommunity(base, room, pubKey) @@ -327,7 +327,7 @@ open class Storage( setRecipientApprovedMe(targetRecipient, true) } } - if (message.threadID == null && !targetRecipient.isOpenGroupRecipient) { + if (message.threadID == null && !targetRecipient.isCommunityRecipient) { // open group recipients should explicitly create threads message.threadID = getOrCreateThreadIdFor(targetAddress) } @@ -1289,7 +1289,7 @@ open class Storage( priority = if (isPinned) PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE ) groups.set(newGroupInfo) - } else if (threadRecipient.isOpenGroupRecipient) { + } else if (threadRecipient.isCommunityRecipient) { val openGroup = getOpenGroup(threadID) ?: return val (baseUrl, room, pubKeyHex) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: return val newGroupInfo = groups.getOrConstructCommunityInfo(baseUrl, room, Hex.toStringCondensed(pubKeyHex)).copy ( diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index fd5042086c..e2f9687451 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -18,7 +18,7 @@ package org.thoughtcrime.securesms.database; import static org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX; -import static org.session.libsession.utilities.GroupUtil.OPEN_GROUP_PREFIX; +import static org.session.libsession.utilities.GroupUtil.COMMUNITY_PREFIX; import static org.thoughtcrime.securesms.database.GroupDatabase.GROUP_ID; import android.content.ContentValues; @@ -427,7 +427,7 @@ public class ThreadDatabase extends Database { } Cursor cursor = cursors.size() > 1 ? new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) : cursors.get(0); - setNotifyConverationListListeners(cursor); + setNotifyConversationListListeners(cursor); return cursor; } @@ -491,7 +491,7 @@ public class ThreadDatabase extends Database { } public Cursor getConversationList() { - String where = "(" + MESSAGE_COUNT + " != 0 OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " + + String where = "(" + MESSAGE_COUNT + " != 0 OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + COMMUNITY_PREFIX + "%') " + "AND " + ARCHIVED + " = 0 "; return getConversationList(where); } @@ -502,7 +502,7 @@ public class ThreadDatabase extends Database { } public Cursor getApprovedConversationList() { - String where = "((" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+CLOSED_GROUP_PREFIX+"%') OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " + + String where = "((" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+CLOSED_GROUP_PREFIX+"%') OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + COMMUNITY_PREFIX + "%') " + "AND " + ARCHIVED + " = 0 "; return getConversationList(where); } @@ -516,7 +516,7 @@ public class ThreadDatabase extends Database { } public Cursor getArchivedConversationList() { - String where = "(" + MESSAGE_COUNT + " != 0 OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " + + String where = "(" + MESSAGE_COUNT + " != 0 OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + COMMUNITY_PREFIX + "%') " + "AND " + ARCHIVED + " = 1 "; return getConversationList(where); } @@ -526,7 +526,7 @@ public class ThreadDatabase extends Database { String query = createQuery(where, 0); Cursor cursor = db.rawQuery(query, null); - setNotifyConverationListListeners(cursor); + setNotifyConversationListListeners(cursor); return cursor; } @@ -547,7 +547,7 @@ public class ThreadDatabase extends Database { // edge case where we set the last seen time for a conversation before it loads messages (joining community for example) MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); Recipient forThreadId = getRecipientForThreadId(threadId); - if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && forThreadId != null && forThreadId.isOpenGroupRecipient()) return false; + if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && forThreadId != null && forThreadId.isCommunityRecipient()) return false; SQLiteDatabase db = databaseHelper.getWritableDatabase(); @@ -822,7 +822,7 @@ public class ThreadDatabase extends Database { private boolean deleteThreadOnEmpty(long threadId) { Recipient threadRecipient = getRecipientForThreadId(threadId); - return threadRecipient != null && !threadRecipient.isOpenGroupRecipient(); + return threadRecipient != null && !threadRecipient.isCommunityRecipient(); } private @NonNull String getFormattedBodyFor(@NonNull MessageRecord messageRecord) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt index 702bf33929..82b9f16dcd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt @@ -11,7 +11,6 @@ import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.databinding.FragmentConversationBottomSheetBinding import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory -import org.thoughtcrime.securesms.util.UiModeUtilities import org.thoughtcrime.securesms.util.getConversationUnread import javax.inject.Inject @@ -75,7 +74,7 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto } binding.copyConversationId.visibility = if (!recipient.isGroupRecipient && !recipient.isLocalNumber) View.VISIBLE else View.GONE binding.copyConversationId.setOnClickListener(this) - binding.copyCommunityUrl.visibility = if (recipient.isOpenGroupRecipient) View.VISIBLE else View.GONE + binding.copyCommunityUrl.visibility = if (recipient.isCommunityRecipient) View.VISIBLE else View.GONE binding.copyCommunityUrl.setOnClickListener(this) binding.unMuteNotificationsTextView.isVisible = recipient.isMuted && !recipient.isLocalNumber binding.muteNotificationsTextView.isVisible = !recipient.isMuted && !recipient.isLocalNumber diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 850f065728..41245f9b85 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -496,7 +496,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), manager.setPrimaryClip(clip) Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() } - else if (thread.recipient.isOpenGroupRecipient) { + else if (thread.recipient.isCommunityRecipient) { val threadId = threadDb.getThreadIdIfExistsFor(thread.recipient) ?: return@onCopyConversationId Unit val openGroup = DatabaseComponent.get(this@HomeActivity).lokiThreadDatabase().getOpenGroupChat(threadId) ?: return@onCopyConversationId Unit diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt index 2922044435..1f3f2ff537 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt @@ -21,6 +21,7 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ActivityPathBinding import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.utilities.getColorFromAttr +import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Snode import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.util.GlowViewUtilities diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index acb9caa75f..bd38d0df86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -91,10 +91,10 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { && !threadRecipient.isOpenGroupInboxRecipient && !threadRecipient.isOpenGroupOutboxRecipient - publicKeyTextView.isVisible = !threadRecipient.isOpenGroupRecipient + publicKeyTextView.isVisible = !threadRecipient.isCommunityRecipient && !threadRecipient.isOpenGroupInboxRecipient && !threadRecipient.isOpenGroupOutboxRecipient - messageButton.isVisible = !threadRecipient.isOpenGroupRecipient || IdPrefix.fromValue(publicKey)?.isBlinded() == true + messageButton.isVisible = !threadRecipient.isCommunityRecipient || IdPrefix.fromValue(publicKey)?.isBlinded() == true publicKeyTextView.text = publicKey publicKeyTextView.setOnLongClickListener { val clipboard = diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java index a4871f0fc9..cad3b6f6c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java @@ -53,7 +53,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void setMostRecentSender(Recipient recipient, Recipient threadRecipient) { String displayName = recipient.toShortString(); if (threadRecipient.isGroupRecipient()) { - displayName = getGroupDisplayName(recipient, threadRecipient.isOpenGroupRecipient()); + displayName = getGroupDisplayName(recipient, threadRecipient.isCommunityRecipient()); } if (privacy.isDisplayContact()) { setContentText(context.getString(R.string.MessageNotifier_most_recent_from_s, displayName)); @@ -79,7 +79,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void addMessageBody(@NonNull Recipient sender, Recipient threadRecipient, @Nullable CharSequence body) { String displayName = sender.toShortString(); if (threadRecipient.isGroupRecipient()) { - displayName = getGroupDisplayName(sender, threadRecipient.isOpenGroupRecipient()); + displayName = getGroupDisplayName(sender, threadRecipient.isCommunityRecipient()); } if (privacy.isDisplayMessage()) { SpannableStringBuilder builder = new SpannableStringBuilder(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 891d0bb2df..2aaa593b58 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -125,7 +125,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) { - String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isOpenGroupRecipient()); + String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isCommunityRecipient()); stringBuilder.append(Util.getBoldedString(displayName + ": ")); } @@ -215,7 +215,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) { - String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isOpenGroupRecipient()); + String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isCommunityRecipient()); stringBuilder.append(Util.getBoldedString(displayName + ": ")); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index f6277d1a4a..384f47d4d6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -1,13 +1,22 @@ package org.thoughtcrime.securesms.repository import network.loki.messenger.libsession_util.util.ExpiryMode + import android.content.ContentResolver import android.content.Context + import app.cash.copper.Query import app.cash.copper.flow.observeQuery + import dagger.hilt.android.qualifiers.ApplicationContext + +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map + import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.control.MessageRequestResponse @@ -22,7 +31,10 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.utilities.Log + import org.session.libsignal.utilities.toHexString + import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DraftDatabase import org.thoughtcrime.securesms.database.ExpirationConfigurationDatabase @@ -39,10 +51,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.DatabaseComponent + import javax.inject.Inject -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine interface ConversationRepository { fun maybeGetRecipientForThreadId(threadId: Long): Recipient? @@ -55,37 +65,19 @@ interface ConversationRepository { fun inviteContacts(threadId: Long, contacts: List) fun setBlocked(recipient: Recipient, blocked: Boolean) fun deleteLocally(recipient: Recipient, message: MessageRecord) + fun deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord: MessageRecord) fun setApproved(recipient: Recipient, isApproved: Boolean) - - suspend fun deleteForEveryone( - threadId: Long, - recipient: Recipient, - message: MessageRecord - ): ResultOf - + suspend fun deleteForEveryone(threadId: Long, recipient: Recipient, message: MessageRecord): ResultOf fun buildUnsendRequest(recipient: Recipient, message: MessageRecord): UnsendRequest? - - suspend fun deleteMessageWithoutUnsendRequest( - threadId: Long, - messages: Set - ): ResultOf - + suspend fun deleteMessageWithoutUnsendRequest(threadId: Long, messages: Set): ResultOf suspend fun banUser(threadId: Long, recipient: Recipient): ResultOf - suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): ResultOf - suspend fun deleteThread(threadId: Long): ResultOf - suspend fun deleteMessageRequest(thread: ThreadRecord): ResultOf - suspend fun clearAllMessageRequests(block: Boolean): ResultOf - suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): ResultOf - fun declineMessageRequest(threadId: Long) - fun hasReceived(threadId: Long): Boolean - } class DefaultConversationRepository @Inject constructor( @@ -184,6 +176,15 @@ class DefaultConversationRepository @Inject constructor( messageDataProvider.deleteMessage(message.id, !message.isMms) } + override fun deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord: MessageRecord) { + val threadId = messageRecord.threadId + val senderId = messageRecord.recipient.address.contactIdentifier() + val messageRecordsToRemoveFromLocalStorage = mmsSmsDb.getAllMessageRecordsFromSenderInThread(threadId, senderId) + for (message in messageRecordsToRemoveFromLocalStorage) { + messageDataProvider.deleteMessage(message.id, !message.isMms) + } + } + override fun setApproved(recipient: Recipient, isApproved: Boolean) { storage.setRecipientApproved(recipient, isApproved) } @@ -196,18 +197,38 @@ class DefaultConversationRepository @Inject constructor( buildUnsendRequest(recipient, message)?.let { unsendRequest -> MessageSender.send(unsendRequest, recipient.address) } + val openGroup = lokiThreadDb.getOpenGroupChat(threadId) if (openGroup != null) { - lokiMessageDb.getServerID(message.id, !message.isMms)?.let { messageServerID -> + val serverId = lokiMessageDb.getServerID(message.id, !message.isMms)?.let { messageServerID -> OpenGroupApi.deleteMessage(messageServerID, openGroup.room, openGroup.server) .success { messageDataProvider.deleteMessage(message.id, !message.isMms) continuation.resume(ResultOf.Success(Unit)) }.fail { error -> + Log.w("TAG", "Call to OpenGroupApi.deleteForEveryone failed - attempting to resume..") continuation.resumeWithException(error) } } - } else { + + // If the server ID is null then this message is stuck in limbo (it has likely been + // deleted remotely but that deletion did not occur locally) - so we'll delete the + // message locally to clean up. + if (serverId == null) { + Log.w("ConversationRepository","Found community message without a server ID - deleting locally.") + + // Caution: The bool returned from `deleteMessage` is NOT "Was the message + // successfully deleted?" - it is "Was the thread itself also deleted because + // removing that message resulted in an empty thread?". + if (message.isMms) { + mmsDb.deleteMessage(message.id) + } else { + smsDb.deleteMessage(message.id) + } + } + } + else // If this thread is NOT in a Community + { messageDataProvider.deleteMessage(message.id, !message.isMms) messageDataProvider.getServerHashForMessage(message.id, message.isMms)?.let { serverHash -> var publicKey = recipient.address.serialize() @@ -218,6 +239,7 @@ class DefaultConversationRepository @Inject constructor( .success { continuation.resume(ResultOf.Success(Unit)) }.fail { error -> + Log.w("[onversationRepository", "Call to SnodeAPI.deleteMessage failed - attempting to resume..") continuation.resumeWithException(error) } } @@ -225,7 +247,7 @@ class DefaultConversationRepository @Inject constructor( } override fun buildUnsendRequest(recipient: Recipient, message: MessageRecord): UnsendRequest? { - if (recipient.isOpenGroupRecipient) return null + if (recipient.isCommunityRecipient) return null messageDataProvider.getServerHashForMessage(message.id, message.isMms) ?: return null return UnsendRequest( author = message.takeUnless { it.isOutgoing }?.run { individualRecipient.address.contactIdentifier() } ?: textSecurePreferences.getLocalNumber(), @@ -279,8 +301,10 @@ class DefaultConversationRepository @Inject constructor( override suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): ResultOf = suspendCoroutine { continuation -> + // Note: This sessionId could be the blinded Id val sessionID = recipient.address.toString() val openGroup = lokiThreadDb.getOpenGroupChat(threadId)!! + OpenGroupApi.banAndDeleteAll(sessionID, openGroup.room, openGroup.server) .success { continuation.resume(ResultOf.Success(Unit)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt index 297014d86c..9d10cfdab5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt @@ -193,7 +193,7 @@ object ConfigurationMessageUtilities { while (current != null) { val recipient = current.recipient val contact = when { - recipient.isOpenGroupRecipient -> { + recipient.isCommunityRecipient -> { val openGroup = storage.getOpenGroup(current.threadId) ?: continue val (base, room, pubKey) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: continue convoConfig.getOrConstructCommunity(base, room, pubKey) @@ -279,7 +279,7 @@ object ConfigurationMessageUtilities { @JvmField val DELETE_INACTIVE_ONE_TO_ONES: String = """ - DELETE FROM ${ThreadDatabase.TABLE_NAME} WHERE ${ThreadDatabase.MESSAGE_COUNT} <= 0 AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.CLOSED_GROUP_PREFIX}%' AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.OPEN_GROUP_PREFIX}%' AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.OPEN_GROUP_INBOX_PREFIX}%'; + DELETE FROM ${ThreadDatabase.TABLE_NAME} WHERE ${ThreadDatabase.MESSAGE_COUNT} <= 0 AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.CLOSED_GROUP_PREFIX}%' AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.COMMUNITY_PREFIX}%' AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.COMMUNITY_INBOX_PREFIX}%'; """.trimIndent() } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SharedConfigUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/SharedConfigUtils.kt index b15d82a33e..3984f38b51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SharedConfigUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SharedConfigUtils.kt @@ -14,7 +14,7 @@ fun ConversationVolatileConfig.getConversationUnread(thread: ThreadRecord): Bool return getOneToOne(recipient.address.serialize())?.unread == true } else if (recipient.isClosedGroupRecipient) { return getLegacyClosedGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toGroupString()))?.unread == true - } else if (recipient.isOpenGroupRecipient) { + } else if (recipient.isCommunityRecipient) { val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(thread.threadId) ?: return false return getCommunity(openGroup.server, openGroup.room)?.unread == true } diff --git a/app/src/sharedTest/java/org/thoughtcrime/securesms/NoOpLogger.kt b/app/src/sharedTest/java/org/thoughtcrime/securesms/NoOpLogger.kt new file mode 100644 index 0000000000..998c3b179d --- /dev/null +++ b/app/src/sharedTest/java/org/thoughtcrime/securesms/NoOpLogger.kt @@ -0,0 +1,19 @@ +package org.thoughtcrime.securesms + +import org.session.libsignal.utilities.Log.Logger + +object NoOpLogger: Logger() { + override fun v(tag: String?, message: String?, t: Throwable?) {} + + override fun d(tag: String?, message: String?, t: Throwable?) {} + + override fun i(tag: String?, message: String?, t: Throwable?) {} + + override fun w(tag: String?, message: String?, t: Throwable?) {} + + override fun e(tag: String?, message: String?, t: Throwable?) {} + + override fun wtf(tag: String?, message: String?, t: Throwable?) {} + + override fun blockUntilAllWritesFinished() {} +} \ No newline at end of file diff --git a/app/src/test/java/org/thoughtcrime/securesms/BaseViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/BaseViewModelTest.kt index d73bc91a6d..a0f67fa699 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/BaseViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/BaseViewModelTest.kt @@ -1,10 +1,20 @@ package org.thoughtcrime.securesms import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import org.junit.BeforeClass import org.junit.Rule +import org.session.libsignal.utilities.Log open class BaseViewModelTest: BaseCoroutineTest() { + companion object { + @BeforeClass + @JvmStatic + fun setupLogger() { + Log.initialize(NoOpLogger) + } + } + @get:Rule var instantExecutorRule = InstantTaskExecutorRule() diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt index 22679f311e..a9df5d946e 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt @@ -39,7 +39,7 @@ import kotlin.time.Duration.Companion.minutes private const val THREAD_ID = 1L private const val LOCAL_NUMBER = "05---local---address" private val LOCAL_ADDRESS = Address.fromSerialized(LOCAL_NUMBER) -private const val GROUP_NUMBER = "${GroupUtil.OPEN_GROUP_PREFIX}4133" +private const val GROUP_NUMBER = "${GroupUtil.COMMUNITY_PREFIX}4133" private val GROUP_ADDRESS = Address.fromSerialized(GROUP_NUMBER) @OptIn(ExperimentalCoroutinesApi::class) diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt index b89c62b6d5..37303e29d5 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt @@ -3,12 +3,14 @@ package org.thoughtcrime.securesms.conversation.v2 import com.goterl.lazysodium.utils.KeyPair import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking import org.hamcrest.CoreMatchers.endsWith import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.notNullValue import org.hamcrest.CoreMatchers.nullValue import org.hamcrest.MatcherAssert.assertThat import org.junit.Before +import org.junit.BeforeClass import org.junit.Test import org.mockito.Mockito import org.mockito.Mockito.anyLong @@ -18,7 +20,9 @@ import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.BaseViewModelTest +import org.thoughtcrime.securesms.NoOpLogger import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.repository.ConversationRepository @@ -32,6 +36,7 @@ class ConversationViewModelTest: BaseViewModelTest() { private val threadId = 123L private val edKeyPair = mock() private lateinit var recipient: Recipient + private lateinit var messageRecord: MessageRecord private val viewModel: ConversationViewModel by lazy { ConversationViewModel(threadId, edKeyPair, repository, storage) @@ -40,6 +45,9 @@ class ConversationViewModelTest: BaseViewModelTest() { @Before fun setUp() { recipient = mock() + messageRecord = mock { record -> + whenever(record.individualRecipient).thenReturn(recipient) + } whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient) whenever(repository.recipientUpdateFlow(anyLong())).thenReturn(emptyFlow()) } @@ -144,7 +152,7 @@ class ConversationViewModelTest: BaseViewModelTest() { val error = Throwable() whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(ResultOf.Failure(error)) - viewModel.banAndDeleteAll(recipient) + viewModel.banAndDeleteAll(messageRecord) assertThat(viewModel.uiState.first().uiMessages.first().message, endsWith("$error")) } @@ -153,7 +161,7 @@ class ConversationViewModelTest: BaseViewModelTest() { fun `should emit a message on ban user and delete all success`() = runBlockingTest { whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(ResultOf.Success(Unit)) - viewModel.banAndDeleteAll(recipient) + viewModel.banAndDeleteAll(messageRecord) assertThat( viewModel.uiState.first().uiMessages.first().message, @@ -189,7 +197,7 @@ class ConversationViewModelTest: BaseViewModelTest() { @Test fun `open group recipient should have no blinded recipient`() { - whenever(recipient.isOpenGroupRecipient).thenReturn(true) + whenever(recipient.isCommunityRecipient).thenReturn(true) whenever(recipient.isOpenGroupOutboxRecipient).thenReturn(false) whenever(recipient.isOpenGroupInboxRecipient).thenReturn(false) assertThat(viewModel.blindedRecipient, nullValue()) diff --git a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt index 92ff9190c5..7161d070aa 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt @@ -77,7 +77,7 @@ class Contact(val sessionID: String) { companion object { fun contextForRecipient(recipient: Recipient): ContactContext { - return if (recipient.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR + return if (recipient.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR } } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt index 333c87ba78..271549c410 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt @@ -22,7 +22,7 @@ class OpenGroupDeleteJob(private val messageServerIds: LongArray, private val th override suspend fun execute(dispatcherName: String) { val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider val numberToDelete = messageServerIds.size - Log.d(TAG, "Deleting $numberToDelete messages") + Log.d(TAG, "About to attempt to delete $numberToDelete messages") // FIXME: This entire process should probably run in a transaction (with the attachment deletion happening only if it succeeded) try { @@ -42,6 +42,7 @@ class OpenGroupDeleteJob(private val messageServerIds: LongArray, private val th delegate?.handleJobSucceeded(this, dispatcherName) } catch (e: Exception) { + Log.w(TAG, "OpenGroupDeleteJob failed: $e") delegate?.handleJobFailed(this, dispatcherName, e) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt index f30c1b9168..faad7aeebf 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt @@ -43,14 +43,14 @@ sealed class Destination { val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupID).toHexString() ClosedGroup(groupPublicKey) } - address.isOpenGroup -> { + address.isCommunity -> { val storage = MessagingModuleConfiguration.shared.storage val threadID = storage.getThreadId(address)!! storage.getOpenGroup(threadID)?.let { OpenGroup(roomToken = it.room, server = it.server, fileIds = fileIds) } ?: throw Exception("Missing open group for thread with ID: $threadID.") } - address.isOpenGroupInbox -> { + address.isCommunityInbox -> { val groupInboxId = GroupUtil.getDecodedGroupID(address.serialize()).split("!") OpenGroupInbox( groupInboxId.dropLast(2).joinToString("!"), diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt index a7b5051f51..1f23a1cc87 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt @@ -602,8 +602,7 @@ object OpenGroupApi { // region Message Deletion @JvmStatic fun deleteMessage(serverID: Long, room: String, server: String): Promise { - val request = - Request(verb = DELETE, room = room, server = server, endpoint = Endpoint.RoomMessageIndividual(room, serverID)) + val request = Request(verb = DELETE, room = room, server = server, endpoint = Endpoint.RoomMessageIndividual(room, serverID)) return send(request).map { Log.d("Loki", "Message deletion successful.") } @@ -659,7 +658,9 @@ object OpenGroupApi { } fun banAndDeleteAll(publicKey: String, room: String, server: String): Promise { + val requests = mutableListOf>( + // Ban request BatchRequestInfo( request = BatchRequest( method = POST, @@ -669,6 +670,7 @@ object OpenGroupApi { endpoint = Endpoint.UserBan(publicKey), responseType = object: TypeReference(){} ), + // Delete request BatchRequestInfo( request = BatchRequest(DELETE, "/room/$room/all/$publicKey"), endpoint = Endpoint.RoomDeleteMessages(room, publicKey), diff --git a/libsession/src/main/java/org/session/libsession/utilities/Address.kt b/libsession/src/main/java/org/session/libsession/utilities/Address.kt index e265e28e7d..920b466a8b 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/Address.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/Address.kt @@ -22,17 +22,17 @@ class Address private constructor(address: String) : Parcelable, Comparable