feat: support thread deletion and refactoring a lot of getOrCreateThread references to go via storage or assume they are correctly set to hook into the contact and volatile creation during thread creation

This commit is contained in:
0x330a 2023-03-27 17:23:05 +11:00
parent d3c55fad60
commit f111513211
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
8 changed files with 52 additions and 88 deletions

View File

@ -7,6 +7,7 @@ import android.view.View
import android.widget.LinearLayout
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewUserBinding
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities
@ -51,7 +52,7 @@ class UserView : LinearLayout {
val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithSessionID(publicKey)
return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey
}
val threadID = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(user)
val threadID = MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(user.address)
MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(threadID, context) // FIXME: This is a bad place to do this
val address = user.address.serialize()
binding.profilePictureView.root.glide = glide

View File

@ -237,7 +237,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
it
}
val recipient = Recipient.from(this, address, false)
threadId = threadDb.getOrCreateThreadIdFor(recipient)
threadId = storage.getOrCreateThreadIdFor(recipient.address)
}
} ?: finish()
}

View File

@ -4,11 +4,8 @@ import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.JsonUtil
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
@ -24,12 +21,6 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
}
fun getThreadID(hexEncodedPublicKey: String): Long {
val address = Address.fromSerialized(hexEncodedPublicKey)
val recipient = Recipient.from(context, address, false)
return DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
}
fun getAllOpenGroups(): Map<Long, OpenGroup> {
val database = databaseHelper.readableDatabase
var cursor: Cursor? = null

View File

@ -20,13 +20,11 @@ import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import com.annimon.stream.Stream
import com.google.android.mms.pdu_alt.NotificationInd
import com.google.android.mms.pdu_alt.PduHeaders
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.session.libsession.messaging.messages.signal.IncomingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage
import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage
@ -40,16 +38,13 @@ import org.session.libsession.utilities.Address.Companion.UNKNOWN
import org.session.libsession.utilities.Address.Companion.fromExternal
import org.session.libsession.utilities.Address.Companion.fromSerialized
import org.session.libsession.utilities.Contact
import org.session.libsession.utilities.GroupUtil.doubleEncodeGroupID
import org.session.libsession.utilities.IdentityKeyMismatch
import org.session.libsession.utilities.IdentityKeyMismatchList
import org.session.libsession.utilities.NetworkFailure
import org.session.libsession.utilities.NetworkFailureList
import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled
import org.session.libsession.utilities.Util.toIsoBytes
import org.session.libsession.utilities.Util.toIsoString
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.recipients.RecipientFormattingException
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ThreadUtils.queue
@ -234,34 +229,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
}
}
@Throws(RecipientFormattingException::class, MmsException::class)
private fun getThreadIdFor(retrieved: IncomingMediaMessage): Long {
return if (retrieved.groupId != null) {
val groupRecipients = Recipient.from(
context,
retrieved.groupId,
true
)
get(context).threadDatabase().getOrCreateThreadIdFor(groupRecipients)
} else {
val sender = Recipient.from(
context,
retrieved.from,
true
)
get(context).threadDatabase().getOrCreateThreadIdFor(sender)
}
}
private fun getThreadIdFor(notification: NotificationInd): Long {
val fromString =
if (notification.from != null && notification.from.textString != null) toIsoString(
notification.from.textString
) else ""
val recipient = Recipient.from(context, fromExternal(context, fromString), false)
return get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
}
private fun rawQuery(where: String, arguments: Array<String>?): Cursor {
val database = databaseHelper.readableDatabase
return database.rawQuery(
@ -272,10 +239,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
)
}
fun getMessages(idsAsString: String): Cursor {
return rawQuery(idsAsString, null)
}
fun getMessage(messageId: Long): Cursor {
val cursor = rawQuery(RAW_ID_WHERE, arrayOf(messageId.toString()))
setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId))
@ -633,15 +596,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
serverTimestamp: Long,
runThreadUpdate: Boolean
): Optional<InsertResult> {
var threadId = threadId
if (threadId == -1L || retrieved.isGroupMessage) {
try {
threadId = getThreadIdFor(retrieved)
} catch (e: RecipientFormattingException) {
Log.w("MmsDatabase", e)
if (threadId == -1L) throw MmsException(e)
}
}
if (threadId < 0 ) throw MmsException("No thread ID supplied!")
val contentValues = ContentValues()
contentValues.put(DATE_SENT, retrieved.sentTimeMillis)
contentValues.put(ADDRESS, retrieved.from.serialize())
@ -710,27 +665,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
serverTimestamp: Long,
runThreadUpdate: Boolean
): Optional<InsertResult> {
var threadId = threadId
if (threadId == -1L) {
if (retrieved.isGroup) {
val decodedGroupId: String = if (retrieved is OutgoingExpirationUpdateMessage) {
retrieved.groupId
} else {
(retrieved as OutgoingGroupMediaMessage).groupId
}
val groupId: String
groupId = try {
doubleEncodeGroupID(decodedGroupId)
} catch (e: IOException) {
Log.e(TAG, "Couldn't encrypt group ID")
throw MmsException(e)
}
val group = Recipient.from(context, fromSerialized(groupId), false)
threadId = get(context).threadDatabase().getOrCreateThreadIdFor(group)
} else {
threadId = get(context).threadDatabase().getOrCreateThreadIdFor(retrieved.recipient)
}
}
if (threadId < 0 ) throw MmsException("No thread ID supplied!")
val messageId = insertMessageOutbox(retrieved, threadId, false, null, serverTimestamp, runThreadUpdate)
if (messageId == -1L) {
return Optional.absent()

View File

@ -202,13 +202,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
}
val targetRecipient = Recipient.from(context, targetAddress, false)
if (!targetRecipient.isGroupRecipient) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
if (isUserSender || isUserBlindedSender) {
setRecipientApproved(targetRecipient, true)
} else {
setRecipientApprovedMe(targetRecipient, true)
}
}
if (message.threadID == null && !targetRecipient.isOpenGroupRecipient) {
// open group recipients should explicitly create threads
message.threadID = getOrCreateThreadIdFor(targetAddress)
}
if (message.isMediaMessage() || attachments.isNotEmpty()) {
val quote: Optional<QuoteModel> = if (quotes != null) Optional.of(quotes) else Optional.absent()
val linkPreviews: Optional<List<LinkPreview>> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
@ -222,7 +225,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
it.toSignalPointer()
}
val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews)
mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0, runThreadUpdate)
mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID!!, message.receivedTimestamp ?: 0, runThreadUpdate)
}
if (insertResult.isPresent) {
messageID = insertResult.get().messageId
@ -998,6 +1001,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
profileManager.setProfilePictureURL(context, recipient, url)
}
if (contact.hidden) {
getThreadId(fromSerialized(contact.id))?.let { conversationThreadId ->
deleteConversation(conversationThreadId)
}
}
Log.d("Loki-DBG", "Updated contact $contact")
}
}
@ -1075,8 +1083,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
override fun deleteConversation(threadID: Long) {
// TODO: delete from either contacts / convo volatile or the closed groups
val recipient = getRecipientForThread(threadID)
val threadDB = DatabaseComponent.get(context).threadDatabase()
threadDB.deleteConversation(threadID)
if (recipient != null) {
if (recipient.isContactRecipient) {
// TODO: handle contact
val contacts = configFactory.contacts ?: return
contacts.upsertContact(recipient.address.serialize()) {
this.hidden = true
}
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
} else if (recipient.isClosedGroupRecipient) {
// TODO: handle closed group
}
}
}
override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri {
@ -1094,6 +1115,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
if (recipient.isBlocked) return
val threadId = getThreadId(recipient) ?: return
val mediaMessage = IncomingMediaMessage(
address,
sentTimestamp,
@ -1112,7 +1135,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
Optional.of(message)
)
database.insertSecureDecryptedMessageInbox(mediaMessage, -1, runThreadUpdate = true)
database.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true)
}
override fun insertMessageRequestResponse(response: MessageRequestResponse) {
@ -1131,7 +1154,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
val mmsDb = DatabaseComponent.get(context).mmsDatabase()
val smsDb = DatabaseComponent.get(context).smsDatabase()
val sender = Recipient.from(context, fromSerialized(senderPublicKey), false)
val threadId = threadDB.getOrCreateThreadIdFor(sender)
val threadId = getOrCreateThreadIdFor(sender.address)
val profile = response.profile
if (profile != null) {
val profileManager = SSKEnvironment.shared.profileManager

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.service;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import org.session.libsession.database.StorageProtocol;
import org.session.libsession.messaging.MessagingModuleConfiguration;
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate;
import org.session.libsession.messaging.messages.signal.IncomingMediaMessage;
@ -107,6 +108,10 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
Address groupAddress = Address.fromSerialized(groupID);
recipient = Recipient.from(context, groupAddress, false);
}
Long threadId = MessagingModuleConfiguration.getShared().getStorage().getThreadId(recipient);
if (threadId == null) {
return;
}
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, sentTimestamp, -1,
duration * 1000L, true,
@ -121,7 +126,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
Optional.absent(),
Optional.absent());
//insert the timer update message
database.insertSecureDecryptedMessageInbox(mediaMessage, -1, true);
database.insertSecureDecryptedMessageInbox(mediaMessage, threadId, true);
//set the timer to the conversation
MessagingModuleConfiguration.getShared().getStorage().setExpirationTimer(recipient.getAddress().serialize(), duration);
@ -140,6 +145,10 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
Address address = Address.fromSerialized((message.getSyncTarget() != null && !message.getSyncTarget().isEmpty()) ? message.getSyncTarget() : message.getRecipient());
Recipient recipient = Recipient.from(context, address, false);
if (message.getThreadID() == null || message.getThreadID() < 0) {
StorageProtocol storage = MessagingModuleConfiguration.getShared().getStorage();
message.setThreadID(storage.getOrCreateThreadIdFor(address));
}
try {
OutgoingExpirationUpdateMessage timerUpdateMessage = new OutgoingExpirationUpdateMessage(recipient, sentTimestamp, duration * 1000L, groupId);

View File

@ -13,17 +13,18 @@ inline session::config::Contacts *ptrToContacts(JNIEnv *env, jobject obj) {
inline jobject serialize_contact(JNIEnv *env, session::config::contact_info info) {
jclass contactClass = env->FindClass("network/loki/messenger/libsession_util/util/Contact");
jmethodID constructor = env->GetMethodID(contactClass, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZLnetwork/loki/messenger/libsession_util/util/UserPic;ILnetwork/loki/messenger/libsession_util/util/ExpiryMode;)V");
jmethodID constructor = env->GetMethodID(contactClass, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZZLnetwork/loki/messenger/libsession_util/util/UserPic;ILnetwork/loki/messenger/libsession_util/util/ExpiryMode;)V");
jstring id = env->NewStringUTF(info.session_id.data());
jstring name = env->NewStringUTF(info.name.data());
jstring nickname = env->NewStringUTF(info.nickname.data());
jboolean approved, approvedMe, blocked;
jboolean approved, approvedMe, blocked, hidden;
approved = info.approved;
approvedMe = info.approved_me;
blocked = info.blocked;
hidden = info.hidden;
jobject profilePic = util::serialize_user_pic(env, info.profile_picture);
jobject returnObj = env->NewObject(contactClass, constructor, id, name, nickname, approved,
approvedMe, blocked, profilePic, info.priority,
approvedMe, blocked, hidden, profilePic, info.priority,
util::serialize_expiry(env, info.exp_mode, info.exp_timer));
return returnObj;
}
@ -32,13 +33,14 @@ inline session::config::contact_info
deserialize_contact(JNIEnv *env, jobject info, session::config::Contacts *conf) {
jclass contactClass = env->FindClass("network/loki/messenger/libsession_util/util/Contact");
jfieldID getId, getName, getNick, getApproved, getApprovedMe, getBlocked, getUserPic, getPriority, getExpiry;
jfieldID getId, getName, getNick, getApproved, getApprovedMe, getBlocked, getUserPic, getPriority, getExpiry, getHidden;
getId = env->GetFieldID(contactClass, "id", "Ljava/lang/String;");
getName = env->GetFieldID(contactClass, "name", "Ljava/lang/String;");
getNick = env->GetFieldID(contactClass, "nickname", "Ljava/lang/String;");
getApproved = env->GetFieldID(contactClass, "approved", "Z");
getApprovedMe = env->GetFieldID(contactClass, "approvedMe", "Z");
getBlocked = env->GetFieldID(contactClass, "blocked", "Z");
getHidden = env->GetFieldID(contactClass, "hidden", "Z");
getUserPic = env->GetFieldID(contactClass, "profilePicture",
"Lnetwork/loki/messenger/libsession_util/util/UserPic;");
getPriority = env->GetFieldID(contactClass, "priority", "I");
@ -47,11 +49,12 @@ deserialize_contact(JNIEnv *env, jobject info, session::config::Contacts *conf)
session_id = static_cast<jstring>(env->GetObjectField(info, getId));
name = static_cast<jstring>(env->GetObjectField(info, getName));
nickname = static_cast<jstring>(env->GetObjectField(info, getNick));
bool approved, approvedMe, blocked;
bool approved, approvedMe, blocked, hidden;
int priority = env->GetIntField(info, getPriority);
approved = env->GetBooleanField(info, getApproved);
approvedMe = env->GetBooleanField(info, getApprovedMe);
blocked = env->GetBooleanField(info, getBlocked);
hidden = env->GetBooleanField(info, getHidden);
jobject user_pic = env->GetObjectField(info, getUserPic);
jobject expiry_mode = env->GetObjectField(info, getExpiry);
@ -83,6 +86,7 @@ deserialize_contact(JNIEnv *env, jobject info, session::config::Contacts *conf)
contact_info.approved = approved;
contact_info.approved_me = approvedMe;
contact_info.blocked = blocked;
contact_info.hidden = hidden;
if (!url.empty() && !key.empty()) {
contact_info.profile_picture = session::config::profile_pic(url, key);
} else {

View File

@ -7,6 +7,7 @@ data class Contact(
var approved: Boolean = false,
var approvedMe: Boolean = false,
var blocked: Boolean = false,
var hidden: Boolean = false,
var profilePicture: UserPic = UserPic.DEFAULT,
var priority: Int = 0,
var expiryMode: ExpiryMode