Allow for MSL entries to be associated with multiple messages.
This commit is contained in:
parent
92e8f9de0e
commit
5372f79c40
6 changed files with 176 additions and 89 deletions
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database
|
|||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.database.model.MessageLogEntry
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
@ -17,41 +18,51 @@ import java.util.UUID
|
|||
/**
|
||||
* Stores a 24-hr buffer of all outgoing messages. Used for the retry logic required for sender key.
|
||||
*
|
||||
* General note: This class is actually two tables -- one to store the entry, and another to store all the devices that were sent it.
|
||||
* General note: This class is actually three tables:
|
||||
* - one to store the entry
|
||||
* - one to store all the devices that were sent it, and
|
||||
* - one to store the set of related messages.
|
||||
*
|
||||
* The general lifecycle of entries in the store goes something like this:
|
||||
* - Upon sending a message, throw an entry in the 'message table' and throw an entry for each recipient you sent it to in the 'recipient table'
|
||||
* - Upon sending a message, put an entry in the 'payload table', an entry for each recipient you sent it to in the 'recipient table', and an entry for each
|
||||
* related message in the 'message table'
|
||||
* - Whenever you get a delivery receipt, delete the entries in the 'recipient table'
|
||||
* - Whenever there's no more records in the 'recipient table' for a given message, delete the entry in the 'message table'
|
||||
* - Whenever you delete a message, delete the entry in the 'message table'
|
||||
* - Whenever you delete a message, delete the relevant entries from the 'payload table'
|
||||
* - Whenever you read an entry from the table, first trim off all the entries that are too old
|
||||
*
|
||||
* Because of all of this, you can be sure that if an entry is in this store, it's safe to resend to someone upon request
|
||||
*
|
||||
* Worth noting that we use triggers + foreign keys to make sure entries in this table are properly cleaned up. Triggers for when you delete a message, and
|
||||
* a cascading delete foreign key between these two tables.
|
||||
* cascading delete foreign keys between these three tables.
|
||||
*
|
||||
* Performance considerations:
|
||||
* - The most common operations by far are:
|
||||
* - Inserting into the table
|
||||
* - Deleting a recipient (in response to a delivery receipt)
|
||||
* - We should also optimize for when we delete messages from the sms/mms tables, since you can delete a bunch at once
|
||||
* - We *don't* really need to optimize for retrieval, since that happens very infrequently. In particular, we don't want to slow down inserts in order to
|
||||
* improve retrieval time. That means we shouldn't be adding indexes that optimize for retrieval.
|
||||
*/
|
||||
class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLCipherOpenHelper?) : Database(context, databaseHelper) {
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATE_TABLE: Array<String> = arrayOf(MessageTable.CREATE_TABLE, RecipientTable.CREATE_TABLE)
|
||||
val CREATE_TABLE: Array<String> = arrayOf(PayloadTable.CREATE_TABLE, RecipientTable.CREATE_TABLE, MessageTable.CREATE_TABLE)
|
||||
|
||||
@JvmField
|
||||
val CREATE_INDEXES: Array<String> = MessageTable.CREATE_INDEXES + RecipientTable.CREATE_INDEXES
|
||||
val CREATE_INDEXES: Array<String> = PayloadTable.CREATE_INDEXES + RecipientTable.CREATE_INDEXES + MessageTable.CREATE_INDEXES
|
||||
|
||||
@JvmField
|
||||
val CREATE_TRIGGERS: Array<String> = MessageTable.CREATE_TRIGGERS
|
||||
val CREATE_TRIGGERS: Array<String> = PayloadTable.CREATE_TRIGGERS
|
||||
}
|
||||
|
||||
private object MessageTable {
|
||||
const val TABLE_NAME = "message_send_log"
|
||||
private object PayloadTable {
|
||||
const val TABLE_NAME = "msl_payload"
|
||||
|
||||
const val ID = "_id"
|
||||
const val DATE_SENT = "date_sent"
|
||||
const val CONTENT = "content"
|
||||
const val RELATED_MESSAGE_ID = "related_message_id"
|
||||
const val IS_RELATED_MESSAGE_MMS = "is_related_message_mms"
|
||||
const val CONTENT_HINT = "content_hint"
|
||||
|
||||
const val CREATE_TABLE = """
|
||||
|
@ -59,54 +70,75 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
|
|||
$ID INTEGER PRIMARY KEY,
|
||||
$DATE_SENT INTEGER NOT NULL,
|
||||
$CONTENT BLOB NOT NULL,
|
||||
$RELATED_MESSAGE_ID INTEGER DEFAULT -1,
|
||||
$IS_RELATED_MESSAGE_MMS INTEGER DEFAULT 0,
|
||||
$CONTENT_HINT INTEGER NOT NULL
|
||||
)
|
||||
"""
|
||||
|
||||
@JvmField
|
||||
/** Created for [deleteEntriesForRecipient] */
|
||||
val CREATE_INDEXES = arrayOf(
|
||||
"CREATE INDEX message_log_date_sent_index ON $TABLE_NAME ($DATE_SENT)",
|
||||
"CREATE INDEX message_log_related_message_index ON $TABLE_NAME ($RELATED_MESSAGE_ID, $IS_RELATED_MESSAGE_MMS)"
|
||||
"CREATE INDEX msl_payload_date_sent_index ON $TABLE_NAME ($DATE_SENT)",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val CREATE_TRIGGERS = arrayOf(
|
||||
"""
|
||||
CREATE TRIGGER msl_sms_delete AFTER DELETE ON ${SmsDatabase.TABLE_NAME}
|
||||
BEGIN
|
||||
DELETE FROM $TABLE_NAME WHERE $RELATED_MESSAGE_ID = old.${SmsDatabase.ID} AND $IS_RELATED_MESSAGE_MMS = 0;
|
||||
DELETE FROM $TABLE_NAME WHERE $ID IN (SELECT ${MessageTable.PAYLOAD_ID} FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.MESSAGE_ID} = old.${SmsDatabase.ID} AND ${MessageTable.IS_MMS} = 0);
|
||||
END
|
||||
""",
|
||||
"""
|
||||
CREATE TRIGGER msl_mms_delete AFTER DELETE ON ${MmsDatabase.TABLE_NAME}
|
||||
BEGIN
|
||||
DELETE FROM $TABLE_NAME WHERE $RELATED_MESSAGE_ID = old.${MmsDatabase.ID} AND $IS_RELATED_MESSAGE_MMS = 1;
|
||||
DELETE FROM $TABLE_NAME WHERE $ID IN (SELECT ${MessageTable.PAYLOAD_ID} FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.MESSAGE_ID} = old.${MmsDatabase.ID} AND ${MessageTable.IS_MMS} = 1);
|
||||
END
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
private object RecipientTable {
|
||||
const val TABLE_NAME = "message_send_log_recipients"
|
||||
const val TABLE_NAME = "msl_recipient"
|
||||
|
||||
const val ID = "_id"
|
||||
const val MESSAGE_LOG_ID = "message_send_log_id"
|
||||
const val PAYLOAD_ID = "payload_id"
|
||||
const val RECIPIENT_ID = "recipient_id"
|
||||
const val DEVICE = "device"
|
||||
|
||||
const val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ID INTEGER PRIMARY KEY,
|
||||
$MESSAGE_LOG_ID INTEGER NOT NULL REFERENCES ${MessageTable.TABLE_NAME} (${MessageTable.ID}) ON DELETE CASCADE,
|
||||
$PAYLOAD_ID INTEGER NOT NULL REFERENCES ${PayloadTable.TABLE_NAME} (${PayloadTable.ID}) ON DELETE CASCADE,
|
||||
$RECIPIENT_ID INTEGER NOT NULL,
|
||||
$DEVICE INTEGER NOT NULL
|
||||
)
|
||||
"""
|
||||
|
||||
/** Created for [deleteEntriesForRecipient] */
|
||||
val CREATE_INDEXES = arrayOf(
|
||||
"CREATE INDEX message_send_log_recipients_recipient_index ON $TABLE_NAME ($RECIPIENT_ID, $DEVICE)"
|
||||
"CREATE INDEX msl_recipient_recipient_index ON $TABLE_NAME ($RECIPIENT_ID, $DEVICE, $PAYLOAD_ID)",
|
||||
"CREATE INDEX msl_recipient_payload_index ON $TABLE_NAME ($PAYLOAD_ID)"
|
||||
)
|
||||
}
|
||||
|
||||
private object MessageTable {
|
||||
const val TABLE_NAME = "msl_message"
|
||||
|
||||
const val ID = "_id"
|
||||
const val PAYLOAD_ID = "payload_id"
|
||||
const val MESSAGE_ID = "message_id"
|
||||
const val IS_MMS = "is_mms"
|
||||
|
||||
const val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ID INTEGER PRIMARY KEY,
|
||||
$PAYLOAD_ID INTEGER NOT NULL REFERENCES ${PayloadTable.TABLE_NAME} (${PayloadTable.ID}) ON DELETE CASCADE,
|
||||
$MESSAGE_ID INTEGER NOT NULL,
|
||||
$IS_MMS INTEGER NOT NULL
|
||||
)
|
||||
"""
|
||||
|
||||
/** Created for [PayloadTable.CREATE_TRIGGERS] and [deleteAllRelatedToMessage] */
|
||||
val CREATE_INDEXES = arrayOf(
|
||||
"CREATE INDEX msl_message_message_index ON $TABLE_NAME ($MESSAGE_ID, $IS_MMS, $PAYLOAD_ID)"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -115,7 +147,7 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
|
|||
|
||||
if (sendMessageResult.isSuccess && sendMessageResult.success.content.isPresent) {
|
||||
val recipientDevice = listOf(RecipientDevice(recipientId, sendMessageResult.success.devices))
|
||||
insert(recipientDevice, sentTimestamp, sendMessageResult.success.content.get(), contentHint, relatedMessageId, isRelatedMessageMms)
|
||||
insert(recipientDevice, sentTimestamp, sendMessageResult.success.content.get(), contentHint, listOf(MessageId(relatedMessageId, isRelatedMessageMms)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,35 +172,44 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
|
|||
|
||||
val content: SignalServiceProtos.Content = results.first { it.isSuccess && it.success.content.isPresent }.success.content.get()
|
||||
|
||||
insert(recipientDevices, sentTimestamp, content, contentHint, relatedMessageId, isRelatedMessageMms)
|
||||
insert(recipientDevices, sentTimestamp, content, contentHint, listOf(MessageId(relatedMessageId, isRelatedMessageMms)))
|
||||
}
|
||||
|
||||
private fun insert(recipients: List<RecipientDevice>, dateSent: Long, content: SignalServiceProtos.Content, contentHint: ContentHint, relatedMessageId: Long, isRelatedMessageMms: Boolean) {
|
||||
private fun insert(recipients: List<RecipientDevice>, dateSent: Long, content: SignalServiceProtos.Content, contentHint: ContentHint, messageIds: List<MessageId>) {
|
||||
val db = databaseHelper.writableDatabase
|
||||
|
||||
db.beginTransaction()
|
||||
try {
|
||||
val logValues = ContentValues().apply {
|
||||
put(MessageTable.DATE_SENT, dateSent)
|
||||
put(MessageTable.CONTENT, content.toByteArray())
|
||||
put(MessageTable.CONTENT_HINT, contentHint.type)
|
||||
put(MessageTable.RELATED_MESSAGE_ID, relatedMessageId)
|
||||
put(MessageTable.IS_RELATED_MESSAGE_MMS, if (isRelatedMessageMms) 1 else 0)
|
||||
val payloadValues = ContentValues().apply {
|
||||
put(PayloadTable.DATE_SENT, dateSent)
|
||||
put(PayloadTable.CONTENT, content.toByteArray())
|
||||
put(PayloadTable.CONTENT_HINT, contentHint.type)
|
||||
}
|
||||
|
||||
val messageLogId: Long = db.insert(MessageTable.TABLE_NAME, null, logValues)
|
||||
val payloadId: Long = db.insert(PayloadTable.TABLE_NAME, null, payloadValues)
|
||||
|
||||
recipients.forEach { recipientDevice ->
|
||||
recipientDevice.devices.forEach { device ->
|
||||
val recipientValues = ContentValues()
|
||||
recipientValues.put(RecipientTable.MESSAGE_LOG_ID, messageLogId)
|
||||
recipientValues.put(RecipientTable.RECIPIENT_ID, recipientDevice.recipientId.serialize())
|
||||
recipientValues.put(RecipientTable.DEVICE, device)
|
||||
val recipientValues = ContentValues().apply {
|
||||
put(RecipientTable.PAYLOAD_ID, payloadId)
|
||||
put(RecipientTable.RECIPIENT_ID, recipientDevice.recipientId.serialize())
|
||||
put(RecipientTable.DEVICE, device)
|
||||
}
|
||||
|
||||
db.insert(RecipientTable.TABLE_NAME, null, recipientValues)
|
||||
}
|
||||
}
|
||||
|
||||
messageIds.forEach { messageId ->
|
||||
val messageValues = ContentValues().apply {
|
||||
put(MessageTable.PAYLOAD_ID, payloadId)
|
||||
put(MessageTable.MESSAGE_ID, messageId.id)
|
||||
put(MessageTable.IS_MMS, if (messageId.mms) 1 else 0)
|
||||
}
|
||||
|
||||
db.insert(MessageTable.TABLE_NAME, null, messageValues)
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful()
|
||||
} finally {
|
||||
db.endTransaction()
|
||||
|
@ -181,20 +222,34 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
|
|||
trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge())
|
||||
|
||||
val db = databaseHelper.readableDatabase
|
||||
val table = "${MessageTable.TABLE_NAME} LEFT JOIN ${RecipientTable.TABLE_NAME} ON ${MessageTable.TABLE_NAME}.${MessageTable.ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.MESSAGE_LOG_ID}"
|
||||
val query = "${MessageTable.DATE_SENT} = ? AND ${RecipientTable.RECIPIENT_ID} = ? AND ${RecipientTable.DEVICE} = ?"
|
||||
val table = "${PayloadTable.TABLE_NAME} LEFT JOIN ${RecipientTable.TABLE_NAME} ON ${PayloadTable.TABLE_NAME}.${PayloadTable.ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.PAYLOAD_ID}"
|
||||
val query = "${PayloadTable.DATE_SENT} = ? AND ${RecipientTable.RECIPIENT_ID} = ? AND ${RecipientTable.DEVICE} = ?"
|
||||
val args = SqlUtil.buildArgs(dateSent, recipientId, device)
|
||||
|
||||
db.query(table, null, query, args, null, null, null).use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
return MessageLogEntry(
|
||||
recipientId = RecipientId.from(CursorUtil.requireLong(cursor, RecipientTable.RECIPIENT_ID)),
|
||||
dateSent = CursorUtil.requireLong(cursor, MessageTable.DATE_SENT),
|
||||
content = SignalServiceProtos.Content.parseFrom(CursorUtil.requireBlob(cursor, MessageTable.CONTENT)),
|
||||
contentHint = ContentHint.fromType(CursorUtil.requireInt(cursor, MessageTable.CONTENT_HINT)),
|
||||
relatedMessageId = CursorUtil.requireLong(cursor, MessageTable.RELATED_MESSAGE_ID),
|
||||
isRelatedMessageMms = CursorUtil.requireBoolean(cursor, MessageTable.IS_RELATED_MESSAGE_MMS)
|
||||
)
|
||||
db.query(table, null, query, args, null, null, null).use { entryCursor ->
|
||||
if (entryCursor.moveToFirst()) {
|
||||
val payloadId = CursorUtil.requireLong(entryCursor, RecipientTable.PAYLOAD_ID)
|
||||
|
||||
db.query(MessageTable.TABLE_NAME, null, "${MessageTable.PAYLOAD_ID} = ?", SqlUtil.buildArgs(payloadId), null, null, null).use { messageCursor ->
|
||||
val messageIds: MutableList<MessageId> = mutableListOf()
|
||||
|
||||
while (messageCursor.moveToNext()) {
|
||||
messageIds.add(
|
||||
MessageId(
|
||||
id = CursorUtil.requireLong(messageCursor, MessageTable.MESSAGE_ID),
|
||||
mms = CursorUtil.requireBoolean(messageCursor, MessageTable.IS_MMS)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return MessageLogEntry(
|
||||
recipientId = RecipientId.from(CursorUtil.requireLong(entryCursor, RecipientTable.RECIPIENT_ID)),
|
||||
dateSent = CursorUtil.requireLong(entryCursor, PayloadTable.DATE_SENT),
|
||||
content = SignalServiceProtos.Content.parseFrom(CursorUtil.requireBlob(entryCursor, PayloadTable.CONTENT)),
|
||||
contentHint = ContentHint.fromType(CursorUtil.requireInt(entryCursor, PayloadTable.CONTENT_HINT)),
|
||||
relatedMessages = messageIds
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,10 +260,10 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
|
|||
if (!FeatureFlags.senderKey()) return
|
||||
|
||||
val db = databaseHelper.writableDatabase
|
||||
val query = "${MessageTable.RELATED_MESSAGE_ID} = ? AND ${MessageTable.IS_RELATED_MESSAGE_MMS} = ?"
|
||||
val query = "${PayloadTable.ID} IN (SELECT ${MessageTable.PAYLOAD_ID} FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.MESSAGE_ID} = ? AND ${MessageTable.IS_MMS} = ?)"
|
||||
val args = SqlUtil.buildArgs(messageId, if (mms) 1 else 0)
|
||||
|
||||
db.delete(MessageTable.TABLE_NAME, query, args)
|
||||
db.delete(PayloadTable.TABLE_NAME, query, args)
|
||||
}
|
||||
|
||||
fun deleteEntryForRecipient(dateSent: Long, recipientId: RecipientId, device: Int) {
|
||||
|
@ -227,17 +282,17 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
|
|||
val query = """
|
||||
${RecipientTable.RECIPIENT_ID} = ? AND
|
||||
${RecipientTable.DEVICE} = ? AND
|
||||
${RecipientTable.MESSAGE_LOG_ID} IN (
|
||||
SELECT ${MessageTable.ID}
|
||||
FROM ${MessageTable.TABLE_NAME}
|
||||
WHERE ${MessageTable.DATE_SENT} IN (${dateSent.joinToString(",")})
|
||||
${RecipientTable.PAYLOAD_ID} IN (
|
||||
SELECT ${PayloadTable.ID}
|
||||
FROM ${PayloadTable.TABLE_NAME}
|
||||
WHERE ${PayloadTable.DATE_SENT} IN (${dateSent.joinToString(",")})
|
||||
)"""
|
||||
val args = SqlUtil.buildArgs(recipientId, device)
|
||||
|
||||
db.delete(RecipientTable.TABLE_NAME, query, args)
|
||||
|
||||
val cleanQuery = "${MessageTable.ID} NOT IN (SELECT ${RecipientTable.MESSAGE_LOG_ID} FROM ${RecipientTable.TABLE_NAME})"
|
||||
db.delete(MessageTable.TABLE_NAME, cleanQuery, null)
|
||||
val cleanQuery = "${PayloadTable.ID} NOT IN (SELECT ${RecipientTable.PAYLOAD_ID} FROM ${RecipientTable.TABLE_NAME})"
|
||||
db.delete(PayloadTable.TABLE_NAME, cleanQuery, null)
|
||||
|
||||
db.setTransactionSuccessful()
|
||||
} finally {
|
||||
|
@ -248,17 +303,17 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SQLC
|
|||
fun deleteAll() {
|
||||
if (!FeatureFlags.senderKey()) return
|
||||
|
||||
databaseHelper.writableDatabase.delete(MessageTable.TABLE_NAME, null, null)
|
||||
databaseHelper.writableDatabase.delete(PayloadTable.TABLE_NAME, null, null)
|
||||
}
|
||||
|
||||
fun trimOldMessages(currentTime: Long, maxAge: Long) {
|
||||
if (!FeatureFlags.senderKey()) return
|
||||
|
||||
val db = databaseHelper.writableDatabase
|
||||
val query = "${MessageTable.DATE_SENT} < ?"
|
||||
val query = "${PayloadTable.DATE_SENT} < ?"
|
||||
val args = SqlUtil.buildArgs(currentTime - maxAge)
|
||||
|
||||
db.delete(MessageTable.TABLE_NAME, query, args)
|
||||
db.delete(PayloadTable.TABLE_NAME, query, args)
|
||||
}
|
||||
|
||||
private data class RecipientDevice(val recipientId: RecipientId, val devices: List<Int>)
|
||||
|
|
|
@ -201,8 +201,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
|
|||
private static final int SENDER_KEY = 103;
|
||||
private static final int MESSAGE_DUPE_INDEX = 104;
|
||||
private static final int MESSAGE_LOG = 105;
|
||||
private static final int MESSAGE_LOG_2 = 106;
|
||||
|
||||
private static final int DATABASE_VERSION = 105;
|
||||
private static final int DATABASE_VERSION = 106;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
|
@ -1598,6 +1599,41 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
|
|||
db.execSQL("CREATE INDEX message_send_log_recipients_recipient_index ON message_send_log_recipients (recipient_id, device)");
|
||||
}
|
||||
|
||||
if (oldVersion < MESSAGE_LOG_2) {
|
||||
db.execSQL("DROP TABLE message_send_log");
|
||||
db.execSQL("DROP INDEX IF EXISTS message_log_date_sent_index");
|
||||
db.execSQL("DROP INDEX IF EXISTS message_log_related_message_index");
|
||||
db.execSQL("DROP TRIGGER msl_sms_delete");
|
||||
db.execSQL("DROP TRIGGER msl_mms_delete");
|
||||
db.execSQL("DROP TABLE message_send_log_recipients");
|
||||
db.execSQL("DROP INDEX IF EXISTS message_send_log_recipients_recipient_index");
|
||||
|
||||
db.execSQL("CREATE TABLE msl_payload (_id INTEGER PRIMARY KEY, " +
|
||||
"date_sent INTEGER NOT NULL, " +
|
||||
"content BLOB NOT NULL, " +
|
||||
"content_hint INTEGER NOT NULL)");
|
||||
|
||||
db.execSQL("CREATE INDEX msl_payload_date_sent_index ON msl_payload (date_sent)");
|
||||
|
||||
db.execSQL("CREATE TABLE msl_recipient (_id INTEGER PRIMARY KEY, " +
|
||||
"payload_id INTEGER NOT NULL REFERENCES msl_payload (_id) ON DELETE CASCADE, " +
|
||||
"recipient_id INTEGER NOT NULL, " +
|
||||
"device INTEGER NOT NULL)");
|
||||
|
||||
db.execSQL("CREATE INDEX msl_recipient_recipient_index ON msl_recipient (recipient_id, device, payload_id)");
|
||||
db.execSQL("CREATE INDEX msl_recipient_payload_index ON msl_recipient (payload_id)");
|
||||
|
||||
db.execSQL("CREATE TABLE msl_message (_id INTEGER PRIMARY KEY, " +
|
||||
"payload_id INTEGER NOT NULL REFERENCES msl_payload (_id) ON DELETE CASCADE, " +
|
||||
"message_id INTEGER NOT NULL, " +
|
||||
"is_mms INTEGER NOT NULL)");
|
||||
|
||||
db.execSQL("CREATE INDEX msl_message_message_index ON msl_message (message_id, is_mms, payload_id)");
|
||||
|
||||
db.execSQL("CREATE TRIGGER msl_sms_delete AFTER DELETE ON sms BEGIN DELETE FROM msl_payload WHERE _id IN (SELECT payload_id FROM msl_message WHERE message_id = old._id AND is_mms = 0); END");
|
||||
db.execSQL("CREATE TRIGGER msl_mms_delete AFTER DELETE ON mms BEGIN DELETE FROM msl_payload WHERE _id IN (SELECT payload_id FROM msl_message WHERE message_id = old._id AND is_mms = 1); END");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
/**
|
||||
* Represents a pair of values that can be used to find a message. Because we have two tables,
|
||||
* that means this has both the primary key and a boolean indicating which table it's in.
|
||||
*/
|
||||
data class MessageId(
|
||||
val id: Long,
|
||||
@get:JvmName("isMms") val mms: Boolean
|
||||
)
|
|
@ -12,10 +12,9 @@ data class MessageLogEntry(
|
|||
val dateSent: Long,
|
||||
val content: SignalServiceProtos.Content,
|
||||
val contentHint: ContentHint,
|
||||
val relatedMessageId: Long,
|
||||
val isRelatedMessageMms: Boolean,
|
||||
val relatedMessages: List<MessageId>
|
||||
) {
|
||||
val hasRelatedMessage: Boolean
|
||||
@JvmName("hasRelatedMessage")
|
||||
get() = relatedMessageId > 0
|
||||
get() = relatedMessages.isNotEmpty()
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ public final class SenderKeyDistributionSendJob extends BaseJob {
|
|||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.setMaxInstancesForQueue(1)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase;
|
|||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.database.model.MessageLogEntry;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
|
@ -1795,9 +1796,10 @@ public final class MessageContentProcessor {
|
|||
return;
|
||||
}
|
||||
|
||||
DistributionId distributionId = DatabaseFactory.getGroupDatabase(context).getOrCreateDistributionId(threadRecipient.requireGroupId().requireV2());
|
||||
|
||||
GroupId.V2 groupId = threadRecipient.requireGroupId().requireV2();
|
||||
DistributionId distributionId = DatabaseFactory.getGroupDatabase(context).getOrCreateDistributionId(groupId);
|
||||
SignalProtocolAddress requesterAddress = new SignalProtocolAddress(requester.requireUuid().toString(), decryptionErrorMessage.getDeviceId());
|
||||
|
||||
DatabaseFactory.getSenderKeySharedDatabase(context).delete(distributionId, Collections.singleton(requesterAddress));
|
||||
|
||||
if (messageLogEntry != null) {
|
||||
|
@ -1807,29 +1809,11 @@ public final class MessageContentProcessor {
|
|||
messageLogEntry.getDateSent(),
|
||||
messageLogEntry.getContent(),
|
||||
messageLogEntry.getContentHint(),
|
||||
threadRecipient.requireGroupId().requireV2(),
|
||||
groupId,
|
||||
distributionId));
|
||||
} else {
|
||||
warn(content.getTimestamp(), "[RetryReceipt-SK] Unable to find MSL entry for " + requester.getId() + " with timestamp " + sentTimestamp + ".");
|
||||
|
||||
if (!content.getGroupId().isPresent()) {
|
||||
warn(content.getTimestamp(), "[RetryReceipt-SK] No groupId on the Content, so we cannot send them a SenderKeyDistributionMessage.");
|
||||
return;
|
||||
}
|
||||
|
||||
GroupId groupId;
|
||||
try {
|
||||
groupId = GroupId.push(content.getGroupId().get());
|
||||
} catch (BadGroupIdException e) {
|
||||
warn(String.valueOf(content.getTimestamp()), "[RetryReceipt-SK] Bad groupId!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!groupId.isV2()) {
|
||||
warn(String.valueOf(content.getTimestamp()), "[RetryReceipt-SK] Not a valid GV2 ID!");
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<GroupRecord> groupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(groupId);
|
||||
|
||||
if (!groupRecord.isPresent()) {
|
||||
|
@ -1875,12 +1859,14 @@ public final class MessageContentProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
private static @Nullable MessageRecord findRetryReceiptRelatedMessage(@NonNull Context context, @Nullable MessageLogEntry messageLogEntry, long sentTimestamp) {
|
||||
private @Nullable MessageRecord findRetryReceiptRelatedMessage(@NonNull Context context, @Nullable MessageLogEntry messageLogEntry, long sentTimestamp) {
|
||||
if (messageLogEntry != null && messageLogEntry.hasRelatedMessage()) {
|
||||
if (messageLogEntry.isRelatedMessageMms()) {
|
||||
return DatabaseFactory.getMmsDatabase(context).getMessageRecordOrNull(messageLogEntry.getRelatedMessageId());
|
||||
MessageId relatedMessage = messageLogEntry.getRelatedMessages().get(0);
|
||||
|
||||
if (relatedMessage.isMms()) {
|
||||
return DatabaseFactory.getMmsDatabase(context).getMessageRecordOrNull(relatedMessage.getId());
|
||||
} else {
|
||||
return DatabaseFactory.getSmsDatabase(context).getMessageRecordOrNull(messageLogEntry.getRelatedMessageId());
|
||||
return DatabaseFactory.getSmsDatabase(context).getMessageRecordOrNull(relatedMessage.getId());
|
||||
}
|
||||
} else {
|
||||
return DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(sentTimestamp, Recipient.self().getId());
|
||||
|
|
Loading…
Add table
Reference in a new issue