From 9dfd051e6397e79c427a5b4c82d336f40d3a60ff Mon Sep 17 00:00:00 2001 From: jubb Date: Fri, 9 Jul 2021 15:13:43 +1000 Subject: [PATCH] feat: trust dialog and processing attachments for users after trusting them --- .../attachments/DatabaseAttachmentProvider.kt | 9 +++ .../conversation/v2/ConversationActivityV2.kt | 5 ++ .../v2/components/AlbumThumbnailView.kt | 3 +- .../conversation/v2/dialogs/DownloadDialog.kt | 9 ++- .../v2/messages/UntrustedAttachmentView.kt | 55 +++++++++++++++ .../v2/messages/VisibleMessageContentView.kt | 70 +++++++++++++------ .../v2/messages/VisibleMessageView.kt | 5 +- .../v2/utilities/KThumbnailView.kt | 4 +- .../securesms/database/Storage.kt | 8 ++- .../loki/database/SessionContactDatabase.kt | 9 ++- .../loki/utilities/ActivityUtilities.kt | 2 + .../res/layout/view_untrusted_attachment.xml | 30 ++++++++ app/src/main/res/values/strings.xml | 2 + .../database/MessageDataProvider.kt | 2 + .../libsession/database/StorageProtocol.kt | 1 + .../messaging/jobs/AttachmentDownloadJob.kt | 32 ++++++++- .../libsession/messaging/jobs/JobQueue.kt | 32 +++++---- .../ReceivedMessageHandler.kt | 11 +-- 18 files changed, 230 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/UntrustedAttachmentView.kt create mode 100644 app/src/main/res/layout/view_untrusted_attachment.xml 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 beb5eb4aa8..b2f293ff25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -9,6 +9,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.* import org.session.libsession.utilities.Address import org.session.libsession.utilities.UploadResult import org.session.libsession.utilities.Util +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.messages.SignalServiceAttachment import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceAttachmentStream @@ -92,6 +93,14 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) return message.linkPreviews.firstOrNull()?.attachmentId?.rowId } + override fun getIndividualRecipientForMms(mmsId: Long): Recipient? { + val mmsDb = DatabaseFactory.getMmsDatabase(context) + val message = mmsDb.getMessage(mmsId).use { + mmsDb.readerFor(it).next + } + return message?.individualRecipient + } + override fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream: InputStream) { val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) attachmentDatabase.insertAttachmentsForPlaceholder(messageId, attachmentId, stream) 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 eeb9deacab..defdadf38d 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 @@ -89,6 +89,7 @@ import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager +import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DraftDatabase import org.thoughtcrime.securesms.database.DraftDatabase.Drafts @@ -255,6 +256,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe push(intent, false) } + override fun showDialog(baseDialog: BaseDialog, tag: String?) { + baseDialog.show(supportFragmentManager, tag) + } + private fun setUpRecyclerView() { conversationRecyclerView.adapter = adapter val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index 553d061650..b4d4ab3941 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -15,6 +15,7 @@ import kotlinx.android.synthetic.main.album_thumbnail_view.view.* import network.loki.messenger.R import org.session.libsession.messaging.jobs.AttachmentDownloadJob import org.session.libsession.messaging.jobs.JobQueue +import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.recipients.Recipient @@ -86,7 +87,7 @@ class AlbumThumbnailView : FrameLayout { // hit intersects with this particular child val slide = slides.getOrNull(index) ?: return // only open to downloaded images - if (slide.isPendingDownload) { + if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { // restart download here (slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> val attachmentId = attachment.attachmentId.rowId diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt index fe0423e2e2..fe8cbfcd69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt @@ -9,6 +9,7 @@ import androidx.appcompat.app.AlertDialog import kotlinx.android.synthetic.main.dialog_download.view.* import network.loki.messenger.R import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog import org.thoughtcrime.securesms.database.DatabaseFactory @@ -36,6 +37,12 @@ class DownloadDialog(private val recipient: Recipient) : BaseDialog() { } private fun trust() { - // TODO: Implement + val contactDB = DatabaseFactory.getSessionContactDatabase(requireContext()) + val sessionID = recipient.address.toString() + val contact = contactDB.getContactWithSessionID(sessionID) ?: return + val threadID = DatabaseFactory.getThreadDatabase(requireContext()).getThreadIdIfExistsFor(recipient) + contactDB.setContactIsTrusted(contact, true, threadID) + JobQueue.shared.resumePendingJobs() + dismiss() } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/UntrustedAttachmentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/UntrustedAttachmentView.kt new file mode 100644 index 0000000000..7f286a88e7 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/UntrustedAttachmentView.kt @@ -0,0 +1,55 @@ +package org.thoughtcrime.securesms.conversation.v2.messages + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.LinearLayout +import androidx.annotation.ColorInt +import androidx.core.content.ContextCompat +import kotlinx.android.synthetic.main.view_untrusted_attachment.view.* +import network.loki.messenger.R +import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.conversation.v2.dialogs.DownloadDialog +import org.thoughtcrime.securesms.loki.utilities.ActivityDispatcher +import java.util.* + +class UntrustedAttachmentView: LinearLayout { + + enum class AttachmentType { + AUDIO, + DOCUMENT, + MEDIA + } + + // region Lifecycle + constructor(context: Context) : super(context) { initialize() } + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } + + private fun initialize() { + LayoutInflater.from(context).inflate(R.layout.view_untrusted_attachment, this) + } + // endregion + + // region Updating + fun bind(attachmentType: AttachmentType, @ColorInt textColor: Int) { + val (iconRes, stringRes) = when (attachmentType) { + AttachmentType.AUDIO -> R.drawable.ic_microphone to R.string.Slide_audio + AttachmentType.DOCUMENT -> R.drawable.ic_document_large_light to R.string.document + AttachmentType.MEDIA -> R.drawable.ic_image_white_24dp to R.string.media + } + val iconDrawable = ContextCompat.getDrawable(context,iconRes)!! + iconDrawable.mutate().setTint(textColor) + val text = context.getString(R.string.UntrustedAttachmentView_download_attachment, context.getString(stringRes).toLowerCase(Locale.ROOT)) + + untrustedAttachmentIcon.setImageDrawable(iconDrawable) + untrustedAttachmentTitle.text = text + } + // endregion + + // region Interaction + fun showTrustDialog(recipient: Recipient) { + ActivityDispatcher.get(context)?.showDialog(DownloadDialog(recipient)) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index dd111d0a72..1a9f2d01e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -61,7 +61,7 @@ class VisibleMessageContentView : LinearLayout { // region Updating fun bind(message: MessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean, - glide: GlideRequests, maxWidth: Int, thread: Recipient, searchQuery: String?) { + glide: GlideRequests, maxWidth: Int, thread: Recipient, searchQuery: String?, contactIsTrusted: Boolean) { // Background val background = getBackground(message.isOutgoing, isStartOfMessageCluster, isEndOfMessageCluster) val colorID = if (message.isOutgoing) R.attr.message_sent_background_color else R.attr.message_received_background_color @@ -106,30 +106,54 @@ class VisibleMessageContentView : LinearLayout { } } } else if (message is MmsMessageRecord && message.slideDeck.audioSlide != null) { - val voiceMessageView = VoiceMessageView(context) - voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster) - mainContainer.addView(voiceMessageView) - // We have to use onContentClick (rather than a click listener directly on the voice - // message view) so as to not interfere with all the other gestures. - onContentClick = { voiceMessageView.togglePlayback() } - onContentDoubleTap = { voiceMessageView.handleDoubleTap() } + // Audio attachment + if (contactIsTrusted || message.isOutgoing) { + val voiceMessageView = VoiceMessageView(context) + voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster) + mainContainer.addView(voiceMessageView) + // We have to use onContentClick (rather than a click listener directly on the voice + // message view) so as to not interfere with all the other gestures. + onContentClick = { voiceMessageView.togglePlayback() } + onContentDoubleTap = { voiceMessageView.handleDoubleTap() } + } else { + val untrustedView = UntrustedAttachmentView(context) + untrustedView.bind(UntrustedAttachmentView.AttachmentType.AUDIO, VisibleMessageContentView.getTextColor(context,message)) + mainContainer.addView(untrustedView) + onContentClick = { untrustedView.showTrustDialog(message.individualRecipient) } + } } else if (message is MmsMessageRecord && message.slideDeck.documentSlide != null) { - val documentView = DocumentView(context) - documentView.bind(message, VisibleMessageContentView.getTextColor(context, message)) - mainContainer.addView(documentView) + // Document attachment + if (contactIsTrusted || message.isOutgoing) { + val documentView = DocumentView(context) + documentView.bind(message, VisibleMessageContentView.getTextColor(context, message)) + mainContainer.addView(documentView) + } else { + val untrustedView = UntrustedAttachmentView(context) + untrustedView.bind(UntrustedAttachmentView.AttachmentType.DOCUMENT, VisibleMessageContentView.getTextColor(context,message)) + mainContainer.addView(untrustedView) + onContentClick = { untrustedView.showTrustDialog(message.individualRecipient) } + } } else if (message is MmsMessageRecord && message.slideDeck.asAttachments().isNotEmpty()) { - val albumThumbnailView = AlbumThumbnailView(context) - mainContainer.addView(albumThumbnailView) - // isStart and isEnd of cluster needed for calculating the mask for full bubble image groups - // bind after add view because views are inflated and calculated during bind - albumThumbnailView.bind( - glideRequests = glide, - message = message, - isStart = isStartOfMessageCluster, - isEnd = isEndOfMessageCluster - ) - onContentClick = { event -> - albumThumbnailView.calculateHitObject(event, message, thread) + // Images/Video attachment + if (contactIsTrusted || message.isOutgoing) { + val albumThumbnailView = AlbumThumbnailView(context) + mainContainer.addView(albumThumbnailView) + // isStart and isEnd of cluster needed for calculating the mask for full bubble image groups + // bind after add view because views are inflated and calculated during bind + albumThumbnailView.bind( + glideRequests = glide, + message = message, + isStart = isStartOfMessageCluster, + isEnd = isEndOfMessageCluster + ) + onContentClick = { event -> + albumThumbnailView.calculateHitObject(event, message, thread) + } + } else { + val untrustedView = UntrustedAttachmentView(context) + untrustedView.bind(UntrustedAttachmentView.AttachmentType.MEDIA, VisibleMessageContentView.getTextColor(context,message)) + mainContainer.addView(untrustedView) + onContentClick = { untrustedView.showTrustDialog(message.individualRecipient) } } } else if (message.isOpenGroupInvitation) { val openGroupInvitationView = OpenGroupInvitationView(context) 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 ad90bd5328..86b92b9498 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 @@ -84,6 +84,7 @@ class VisibleMessageView : LinearLayout { val threadDB = DatabaseFactory.getThreadDatabase(context) val thread = threadDB.getRecipientForThreadId(threadID)!! val contactDB = DatabaseFactory.getSessionContactDatabase(context) + val contact = contactDB.getContactWithSessionID(senderSessionID) val isGroupThread = thread.isGroupRecipient val isStartOfMessageCluster = isStartOfMessageCluster(message, previous, isGroupThread) val isEndOfMessageCluster = isEndOfMessageCluster(message, next, isGroupThread) @@ -103,7 +104,7 @@ class VisibleMessageView : LinearLayout { } senderNameTextView.isVisible = isStartOfMessageCluster val context = if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR - senderNameTextView.text = contactDB.getContactWithSessionID(senderSessionID)?.displayName(context) ?: senderSessionID + senderNameTextView.text = contact?.displayName(context) ?: senderSessionID } else { profilePictureContainer.visibility = View.GONE senderNameTextView.visibility = View.GONE @@ -151,7 +152,7 @@ class VisibleMessageView : LinearLayout { var maxWidth = screenWidth - startPadding - endPadding if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width } // Populate content view - messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread, searchQuery) + messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread, searchQuery, isGroupThread || (contact?.isTrusted ?: false)) messageContentView.delegate = contentViewDelegate onDoubleTap = { messageContentView.onContentDoubleTap?.invoke() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt index 031aea1bad..80b233c42d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt @@ -109,8 +109,8 @@ open class KThumbnailView: FrameLayout { this.slide = slide - loadIndicator.isVisible = slide.isInProgress && !slide.isPendingDownload - downloadIndicator.isVisible = slide.isPendingDownload + loadIndicator.isVisible = slide.isInProgress + downloadIndicator.isVisible = slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED dimensDelegate.setDimens(naturalWidth, naturalHeight) invalidate() 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 7fe982a9e3..85d1586a1b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -505,9 +505,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val mmsDb = DatabaseFactory.getMmsDatabase(context) val cursor = mmsDb.getMessage(mmsId) val reader = mmsDb.readerFor(cursor) - val threadId = reader.next.threadId + val threadId = reader.next?.threadId cursor.close() - return threadId + return threadId ?: -1 } override fun getContactWithSessionID(sessionID: String): Contact? { @@ -522,6 +522,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getSessionContactDatabase(context).setContact(contact) } + override fun getRecipientForThread(threadId: Long): Recipient? { + return DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) + } + override fun getRecipientSettings(address: Address): Recipient.RecipientSettings? { val recipientSettings = DatabaseFactory.getRecipientDatabase(context).getRecipientSettings(address) return if (recipientSettings.isPresent) { recipientSettings.get() } else null diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt index 5ccb58df92..4429289f52 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt @@ -47,11 +47,14 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da }.toSet() } - fun setContactIsTrusted(contact: Contact, isTrusted: Boolean) { + fun setContactIsTrusted(contact: Contact, isTrusted: Boolean, threadID: Long) { val database = databaseHelper.writableDatabase val contentValues = ContentValues(1) contentValues.put(Companion.isTrusted, if (isTrusted) 1 else 0) - database.insertOrUpdate(sessionContactTable, contentValues, "$sessionID = ?", arrayOf( contact.sessionID )) + database.update(sessionContactTable, contentValues, "$sessionID = ?", arrayOf( contact.sessionID )) + if (threadID >= 0) { + notifyConversationListeners(threadID) + } notifyConversationListListeners() } @@ -66,7 +69,7 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da contact.profilePictureEncryptionKey?.let { contentValues.put(profilePictureEncryptionKey, Base64.encodeBytes(it)) } - contentValues.put(threadID, threadID) + contentValues.put(threadID, contact.threadID) contentValues.put(isTrusted, if (contact.isTrusted) 1 else 0) database.insertOrUpdate(sessionContactTable, contentValues, "$sessionID = ?", arrayOf( contact.sessionID )) notifyConversationListListeners() diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/ActivityUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/ActivityUtilities.kt index 4986a1ce36..fbddffa5db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/ActivityUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/ActivityUtilities.kt @@ -10,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import network.loki.messenger.R import org.thoughtcrime.securesms.BaseActionBarActivity +import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog fun BaseActionBarActivity.setUpActionBarSessionLogo(hideBackButton: Boolean = false) { val actionbar = supportActionBar!! @@ -65,4 +66,5 @@ interface ActivityDispatcher { fun get(context: Context) = context.getSystemService(SERVICE) as? ActivityDispatcher } fun dispatchIntent(body: (Context)->Intent?) + fun showDialog(baseDialog: BaseDialog, tag: String? = null) } \ No newline at end of file diff --git a/app/src/main/res/layout/view_untrusted_attachment.xml b/app/src/main/res/layout/view_untrusted_attachment.xml new file mode 100644 index 0000000000..c019dc1ace --- /dev/null +++ b/app/src/main/res/layout/view_untrusted_attachment.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8d31dbcf6e..1f11b6e3f7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -874,4 +874,6 @@ %s is blocked. Unblock them? Failed to prepare attachment for sending. + Media + Tap to download %s diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index ca29118878..ef804a6e98 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -3,6 +3,7 @@ package org.session.libsession.database import org.session.libsession.messaging.sending_receiving.attachments.* import org.session.libsession.utilities.Address import org.session.libsession.utilities.UploadResult +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceAttachmentStream import java.io.InputStream @@ -29,4 +30,5 @@ interface MessageDataProvider { fun getMessageBodyFor(timestamp: Long, author: String): String fun getAttachmentIDsFor(messageID: Long): List fun getLinkPreviewAttachmentIDFor(messageID: Long): Long? + fun getIndividualRecipientForMms(mmsId: Long): Recipient? } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index 8a21e61513..9e3ac258b9 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -139,6 +139,7 @@ interface StorageProtocol { fun getContactWithSessionID(sessionID: String): Contact? fun getAllContacts(): Set fun setContact(contact: Contact) + fun getRecipientForThread(threadId: Long): Recipient? fun getRecipientSettings(address: Address): RecipientSettings? fun addContacts(contacts: List) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index 5cbb5d808b..ed08ed2a73 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -17,6 +17,7 @@ import org.session.libsignal.utilities.Log import java.io.File import java.io.FileInputStream import java.io.InputStream +import java.lang.NullPointerException class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) : Job { override var delegate: JobDelegate? = null @@ -26,6 +27,8 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) // Error internal sealed class Error(val description: String) : Exception(description) { object NoAttachment : Error("No such attachment.") + object NoThread: Error("Thread no longer exists") + object NoSender: Error("Thread recipient or sender does not exist") } // Settings @@ -42,8 +45,12 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) override fun execute() { val storage = MessagingModuleConfiguration.shared.storage val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider + val threadID = storage.getThreadIdForMms(databaseMessageID) + val handleFailure: (java.lang.Exception, attachmentId: AttachmentId?) -> Unit = { exception, attachment -> if (exception == Error.NoAttachment + || exception == Error.NoThread + || exception == Error.NoSender || (exception is OnionRequestAPI.HTTPRequestFailedAtDestinationException && exception.statusCode == 400)) { attachment?.let { id -> messageDataProvider.setAttachmentState(AttachmentState.FAILED, id, databaseMessageID) @@ -55,13 +62,34 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) this.handleFailure(exception) } } + + if (threadID < 0) { + Log.e("Loki", "Thread doesn't exist for database message ID $databaseMessageID") + handleFailure(Error.NoThread, null) + return + } + + val threadRecipient = storage.getRecipientForThread(threadID) + val sender = messageDataProvider.getIndividualRecipientForMms(databaseMessageID) + val contact = sender?.address?.let { storage.getContactWithSessionID(it.serialize()) } + if (threadRecipient == null || sender == null || contact == null) { + handleFailure(Error.NoSender, null) + return + } + if (!threadRecipient.isGroupRecipient && (!contact.isTrusted || storage.getUserPublicKey() != sender.address.serialize())) { + Log.e("Loki", "Thread isn't a group recipient, or contact isn't trusted or self-send") + return + } + var tempFile: File? = null try { val attachment = messageDataProvider.getDatabaseAttachment(attachmentID) ?: return handleFailure(Error.NoAttachment, null) + if (attachment.hasData()) { + Log.d("Loki", "The attachment $attachmentID already has data") + } messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachment.attachmentId, this.databaseMessageID) tempFile = createTempFile() - val threadID = storage.getThreadIdForMms(databaseMessageID) val openGroupV2 = storage.getV2OpenGroup(threadID) if (openGroupV2 == null) { DownloadUtilities.downloadFile(tempFile, attachment.url) @@ -94,7 +122,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) handleSuccess() } catch (e: Exception) { tempFile?.delete() - return handleFailure(e) + return handleFailure(e,null) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index 9ced85b110..900be7b25d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -100,6 +100,23 @@ class JobQueue : JobDelegate { Log.d("Loki", "resumed pending send message $id") } + fun resumePendingJobs(typeKey: String) { + val allPendingJobs = MessagingModuleConfiguration.shared.storage.getAllPendingJobs(typeKey) + val pendingJobs = mutableListOf() + for ((id, job) in allPendingJobs) { + if (job == null) { + // Job failed to deserialize, remove it from the DB + handleJobFailedPermanently(id) + } else { + pendingJobs.add(job) + } + } + pendingJobs.sortedBy { it.id }.forEach { job -> + Log.i("Loki", "Resuming pending job of type: ${job::class.simpleName}.") + queue.offer(job) // Offer always called on unlimited capacity + } + } + fun resumePendingJobs() { if (hasResumedPendingJobs) { Log.d("Loki", "resumePendingJobs() should only be called once.") @@ -114,20 +131,7 @@ class JobQueue : JobDelegate { NotifyPNServerJob.KEY ) allJobTypes.forEach { type -> - val allPendingJobs = MessagingModuleConfiguration.shared.storage.getAllPendingJobs(type) - val pendingJobs = mutableListOf() - for ((id, job) in allPendingJobs) { - if (job == null) { - // Job failed to deserialize, remove it from the DB - handleJobFailedPermanently(id) - } else { - pendingJobs.add(job) - } - } - pendingJobs.sortedBy { it.id }.forEach { job -> - Log.i("Loki", "Resuming pending job of type: ${job::class.simpleName}.") - queue.offer(job) // Offer always called on unlimited capacity - } + resumePendingJobs(type) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 6c11a23415..cfbd5605bd 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -221,17 +221,10 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS val messageID = storage.persist(message, quoteModel, linkPreviews, message.groupPublicKey, openGroupID, attachments) ?: throw MessageReceiver.Error.DuplicateMessage // Parse & persist attachments // Start attachment downloads if needed - val contact = message.sender?.let { address -> storage.getContactWithSessionID(address) } storage.getAttachmentsForMessage(messageID).forEach { attachment -> attachment.attachmentId?.let { id -> - // if open group or self-send, then always download attachment - val immediatelyDownload = !openGroupID.isNullOrEmpty() // open group - || userPublicKey == message.sender // self-send - || contact?.isTrusted == true // trusted contact - if (immediatelyDownload) { - val downloadJob = AttachmentDownloadJob(id.rowId, messageID) - JobQueue.shared.add(downloadJob) - } + val downloadJob = AttachmentDownloadJob(id.rowId, messageID) + JobQueue.shared.add(downloadJob) } } val openGroupServerID = message.openGroupServerMessageID