Implement the message send log for sender key retries.

This commit is contained in:
Greyson Parrelli
2021-06-24 11:22:20 -04:00
committed by Cody Henthorne
parent 6502ef64ce
commit f19033a7a2
31 changed files with 1077 additions and 179 deletions

View File

@@ -31,7 +31,6 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.helpers.ClassicOpenHelper;
import org.thoughtcrime.securesms.database.helpers.SQLCipherMigrationHelper;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.PendingRetryReceiptModel;
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -70,6 +69,7 @@ public class DatabaseFactory {
private final PaymentDatabase paymentDatabase;
private final ChatColorsDatabase chatColorsDatabase;
private final EmojiSearchDatabase emojiSearchDatabase;
private final MessageSendLogDatabase messageSendLogDatabase;
public static DatabaseFactory getInstance(Context context) {
if (instance == null) {
@@ -188,16 +188,20 @@ public class DatabaseFactory {
return getInstance(context).paymentDatabase;
}
public static ChatColorsDatabase getChatColorsDatabase(Context context) {
return getInstance(context).chatColorsDatabase;
}
public static EmojiSearchDatabase getEmojiSearchDatabase(Context context) {
return getInstance(context).emojiSearchDatabase;
}
public static SQLiteDatabase getBackupDatabase(Context context) {
return getInstance(context).databaseHelper.getReadableDatabase().getSqlCipherDatabase();
public static MessageSendLogDatabase getMessageLogDatabase(Context context) {
return getInstance(context).messageSendLogDatabase;
}
public static ChatColorsDatabase getChatColorsDatabase(Context context) {
return getInstance(context).chatColorsDatabase;
public static SQLiteDatabase getBackupDatabase(Context context) {
return getInstance(context).databaseHelper.getReadableDatabase().getSqlCipherDatabase();
}
public static void upgradeRestored(Context context, SQLiteDatabase database){
@@ -253,7 +257,8 @@ public class DatabaseFactory {
this.mentionDatabase = new MentionDatabase(context, databaseHelper);
this.paymentDatabase = new PaymentDatabase(context, databaseHelper);
this.chatColorsDatabase = new ChatColorsDatabase(context, databaseHelper);
this.emojiSearchDatabase = new EmojiSearchDatabase(context, databaseHelper);
this.emojiSearchDatabase = new EmojiSearchDatabase(context, databaseHelper);
this.messageSendLogDatabase = new MessageSendLogDatabase(context, databaseHelper);
}
public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret,

View File

@@ -86,6 +86,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
public abstract Cursor getMessageCursor(long messageId);
public abstract OutgoingMediaMessage getOutgoingMessage(long messageId) throws MmsException, NoSuchMessageException;
public abstract MessageRecord getMessageRecord(long messageId) throws NoSuchMessageException;
public abstract @Nullable MessageRecord getMessageRecordOrNull(long messageId);
public abstract Cursor getVerboseMessageCursor(long messageId);
public abstract boolean hasReceivedAnyCallsSince(long threadId, long timestamp);
public abstract @Nullable ViewOnceExpirationInfo getNearestExpiringViewOnceMessage();

View File

@@ -0,0 +1,265 @@
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.MessageLogEntry
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.CursorUtil
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.SqlUtil
import org.whispersystems.signalservice.api.crypto.ContentHint
import org.whispersystems.signalservice.api.messages.SendMessageResult
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
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.
*
* 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'
* - 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 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.
*/
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)
@JvmField
val CREATE_INDEXES: Array<String> = MessageTable.CREATE_INDEXES + RecipientTable.CREATE_INDEXES
@JvmField
val CREATE_TRIGGERS: Array<String> = MessageTable.CREATE_TRIGGERS
}
private object MessageTable {
const val TABLE_NAME = "message_send_log"
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 = """
CREATE TABLE $TABLE_NAME (
$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
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)"
)
@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;
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;
END
"""
)
}
private object RecipientTable {
const val TABLE_NAME = "message_send_log_recipients"
const val ID = "_id"
const val MESSAGE_LOG_ID = "message_send_log_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,
$RECIPIENT_ID INTEGER NOT NULL,
$DEVICE INTEGER NOT NULL
)
"""
val CREATE_INDEXES = arrayOf(
"CREATE INDEX message_send_log_recipients_recipient_index ON $TABLE_NAME ($RECIPIENT_ID, $DEVICE)"
)
}
fun insertIfPossible(recipientId: RecipientId, sentTimestamp: Long, sendMessageResult: SendMessageResult, contentHint: ContentHint, relatedMessageId: Long, isRelatedMessageMms: Boolean) {
if (!FeatureFlags.senderKey()) return
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)
}
}
fun insertIfPossible(sentTimestamp: Long, possibleRecipients: List<Recipient>, results: List<SendMessageResult>, contentHint: ContentHint, relatedMessageId: Long, isRelatedMessageMms: Boolean) {
if (!FeatureFlags.senderKey()) return
val recipientsByUuid: Map<UUID, Recipient> = possibleRecipients.filter(Recipient::hasUuid).associateBy(Recipient::requireUuid, { it })
val recipientsByE164: Map<String, Recipient> = possibleRecipients.filter(Recipient::hasE164).associateBy(Recipient::requireE164, { it })
val recipientDevices: List<RecipientDevice> = results
.filter { it.isSuccess && it.success.content.isPresent }
.map { result ->
val recipient: Recipient =
if (result.address.uuid.isPresent) {
recipientsByUuid[result.address.uuid.get()]!!
} else {
recipientsByE164[result.address.number.get()]!!
}
RecipientDevice(recipient.id, result.success.devices)
}
val content: SignalServiceProtos.Content = results.first { it.isSuccess && it.success.content.isPresent }.success.content.get()
insert(recipientDevices, sentTimestamp, content, contentHint, relatedMessageId, isRelatedMessageMms)
}
private fun insert(recipients: List<RecipientDevice>, dateSent: Long, content: SignalServiceProtos.Content, contentHint: ContentHint, relatedMessageId: Long, isRelatedMessageMms: Boolean) {
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 messageLogId: Long = db.insert(MessageTable.TABLE_NAME, null, logValues)
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)
db.insert(RecipientTable.TABLE_NAME, null, recipientValues)
}
}
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
fun getLogEntry(recipientId: RecipientId, device: Int, dateSent: Long): MessageLogEntry? {
if (!FeatureFlags.senderKey()) return null
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 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)
)
}
}
return null
}
fun deleteAllRelatedToMessage(messageId: Long, mms: Boolean) {
if (!FeatureFlags.senderKey()) return
val db = databaseHelper.writableDatabase
val query = "${MessageTable.RELATED_MESSAGE_ID} = ? AND ${MessageTable.IS_RELATED_MESSAGE_MMS} = ?"
val args = SqlUtil.buildArgs(messageId, if (mms) 1 else 0)
db.delete(MessageTable.TABLE_NAME, query, args)
}
fun deleteEntryForRecipient(dateSent: Long, recipientId: RecipientId, device: Int) {
if (!FeatureFlags.senderKey()) return
deleteEntriesForRecipient(listOf(dateSent), recipientId, device)
}
fun deleteEntriesForRecipient(dateSent: List<Long>, recipientId: RecipientId, device: Int) {
if (!FeatureFlags.senderKey()) return
val db = databaseHelper.writableDatabase
db.beginTransaction()
try {
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(",")})
)"""
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)
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
fun deleteAll() {
if (!FeatureFlags.senderKey()) return
databaseHelper.writableDatabase.delete(MessageTable.TABLE_NAME, null, null)
}
fun trimOldMessages(currentTime: Long, maxAge: Long) {
if (!FeatureFlags.senderKey()) return
val db = databaseHelper.writableDatabase
val query = "${MessageTable.DATE_SENT} < ?"
val args = SqlUtil.buildArgs(currentTime - maxAge)
db.delete(MessageTable.TABLE_NAME, query, args)
}
private data class RecipientDevice(val recipientId: RecipientId, val devices: List<Int>)
}

View File

@@ -768,6 +768,13 @@ public class MmsDatabase extends MessageDatabase {
}
}
@Override
public @Nullable MessageRecord getMessageRecordOrNull(long messageId) {
try (Cursor cursor = rawQuery(RAW_ID_WHERE, new String[] {messageId + ""})) {
return new Reader(cursor).getNext();
}
}
@Override
public Reader getMessages(Collection<Long> messageIds) {
String ids = TextUtils.join(",", messageIds);
@@ -854,23 +861,32 @@ public class MmsDatabase extends MessageDatabase {
public void markAsRemoteDelete(long messageId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(REMOTE_DELETED, 1);
values.putNull(BODY);
values.putNull(QUOTE_BODY);
values.putNull(QUOTE_AUTHOR);
values.putNull(QUOTE_ATTACHMENT);
values.putNull(QUOTE_ID);
values.putNull(LINK_PREVIEWS);
values.putNull(SHARED_CONTACTS);
values.putNull(REACTIONS);
db.update(TABLE_NAME, values, ID_WHERE, new String[] { String.valueOf(messageId) });
long threadId;
DatabaseFactory.getAttachmentDatabase(context).deleteAttachmentsForMessage(messageId);
DatabaseFactory.getMentionDatabase(context).deleteMentionsForMessage(messageId);
db.beginTransaction();
try {
ContentValues values = new ContentValues();
values.put(REMOTE_DELETED, 1);
values.putNull(BODY);
values.putNull(QUOTE_BODY);
values.putNull(QUOTE_AUTHOR);
values.putNull(QUOTE_ATTACHMENT);
values.putNull(QUOTE_ID);
values.putNull(LINK_PREVIEWS);
values.putNull(SHARED_CONTACTS);
values.putNull(REACTIONS);
db.update(TABLE_NAME, values, ID_WHERE, new String[] { String.valueOf(messageId) });
long threadId = getThreadIdForMessage(messageId);
DatabaseFactory.getThreadDatabase(context).update(threadId, false);
DatabaseFactory.getAttachmentDatabase(context).deleteAttachmentsForMessage(messageId);
DatabaseFactory.getMentionDatabase(context).deleteMentionsForMessage(messageId);
DatabaseFactory.getMessageLogDatabase(context).deleteAllRelatedToMessage(messageId, true);
threadId = getThreadIdForMessage(messageId);
DatabaseFactory.getThreadDatabase(context).update(threadId, false);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
notifyConversationListeners(threadId);
}
@@ -1661,6 +1677,7 @@ public class MmsDatabase extends MessageDatabase {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
notifyConversationListeners(threadId);
notifyStickerListeners();
@@ -1773,7 +1790,6 @@ public class MmsDatabase extends MessageDatabase {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
String where = "";
Cursor cursor = null;
for (long threadId : threadIds) {
where += THREAD_ID + " = '" + threadId + "' OR ";
@@ -1781,16 +1797,10 @@ public class MmsDatabase extends MessageDatabase {
where = where.substring(0, where.length() - 4);
try {
cursor = db.query(TABLE_NAME, new String[] {ID}, where, null, null, null, null);
try (Cursor cursor = db.query(TABLE_NAME, new String[] {ID}, where, null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
deleteMessage(cursor.getLong(0));
}
} finally {
if (cursor != null)
cursor.close();
}
}

View File

@@ -414,15 +414,25 @@ public class SmsDatabase extends MessageDatabase {
public void markAsRemoteDelete(long id) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(REMOTE_DELETED, 1);
values.putNull(BODY);
values.putNull(REACTIONS);
db.update(TABLE_NAME, values, ID_WHERE, new String[] { String.valueOf(id) });
long threadId;
long threadId = getThreadIdForMessage(id);
db.beginTransaction();
try {
ContentValues values = new ContentValues();
values.put(REMOTE_DELETED, 1);
values.putNull(BODY);
values.putNull(REACTIONS);
db.update(TABLE_NAME, values, ID_WHERE, new String[] { String.valueOf(id) });
DatabaseFactory.getThreadDatabase(context).update(threadId, false);
threadId = getThreadIdForMessage(id);
DatabaseFactory.getThreadDatabase(context).update(threadId, false);
DatabaseFactory.getMessageLogDatabase(context).deleteAllRelatedToMessage(id, false);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
notifyConversationListeners(threadId);
}
@@ -1299,12 +1309,23 @@ public class SmsDatabase extends MessageDatabase {
public boolean deleteMessage(long messageId) {
Log.d(TAG, "deleteMessage(" + messageId + ")");
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long threadId = getThreadIdForMessage(messageId);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
long threadId;
boolean threadDeleted;
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false, true);
db.beginTransaction();
try {
threadId = getThreadIdForMessage(messageId);
db.delete(TABLE_NAME, ID_WHERE, new String[] { messageId + "" });
threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false, true);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
notifyConversationListeners(threadId);
return threadDeleted;
@@ -1320,6 +1341,15 @@ public class SmsDatabase extends MessageDatabase {
return getSmsMessage(messageId);
}
@Override
public @Nullable MessageRecord getMessageRecordOrNull(long messageId) {
try {
return getSmsMessage(messageId);
} catch (NoSuchMessageException e) {
return null;
}
}
private boolean isDuplicate(IncomingTextMessage message, long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String query = DATE_SENT + " = ? AND " + RECIPIENT_ID + " = ? AND " + THREAD_ID + " = ?";
@@ -1334,6 +1364,7 @@ public class SmsDatabase extends MessageDatabase {
void deleteThread(long threadId) {
Log.d(TAG, "deleteThread(" + threadId + ")");
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, THREAD_ID + " = ?", new String[] {threadId+""});
}

View File

@@ -6,7 +6,7 @@ import net.sqlcipher.database.SQLiteDatabaseHook;
/**
* Standard hook for setting common SQLCipher PRAGMAs.
*/
public final class SqlCipherDatabaseHook implements SQLiteDatabaseHook {
public class SqlCipherDatabaseHook implements SQLiteDatabaseHook {
@Override
public void preKey(SQLiteDatabase db) {
@@ -20,5 +20,6 @@ public final class SqlCipherDatabaseHook implements SQLiteDatabaseHook {
db.rawExecSQL("PRAGMA cipher_memory_security = OFF;");
db.rawExecSQL("PRAGMA kdf_iter = '1';");
db.rawExecSQL("PRAGMA cipher_page_size = 4096;");
db.setForeignKeyConstraintsEnabled(true);
}
}

View File

@@ -960,6 +960,7 @@ public class ThreadDatabase extends Database {
db.beginTransaction();
try {
DatabaseFactory.getMessageLogDatabase(context).deleteAll();
DatabaseFactory.getSmsDatabase(context).deleteAllThreads();
DatabaseFactory.getMmsDatabase(context).deleteAllThreads();
DatabaseFactory.getDraftDatabase(context).clearAllDrafts();

View File

@@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.MentionDatabase;
import org.thoughtcrime.securesms.database.MessageSendLogDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
import org.thoughtcrime.securesms.database.PaymentDatabase;
@@ -199,8 +200,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private static final int EMOJI_SEARCH = 102;
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 DATABASE_VERSION = 104;
private static final int DATABASE_VERSION = 105;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@@ -239,6 +241,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
db.execSQL(EmojiSearchDatabase.CREATE_TABLE);
executeStatements(db, SearchDatabase.CREATE_TABLE);
executeStatements(db, RemappedRecordsDatabase.CREATE_TABLE);
executeStatements(db, MessageSendLogDatabase.CREATE_TABLE);
executeStatements(db, RecipientDatabase.CREATE_INDEXS);
executeStatements(db, SmsDatabase.CREATE_INDEXS);
@@ -252,6 +255,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
executeStatements(db, UnknownStorageIdDatabase.CREATE_INDEXES);
executeStatements(db, MentionDatabase.CREATE_INDEXES);
executeStatements(db, PaymentDatabase.CREATE_INDEXES);
executeStatements(db, MessageSendLogDatabase.CREATE_INDEXES);
executeStatements(db, MessageSendLogDatabase.CREATE_TRIGGERS);
if (context.getDatabasePath(ClassicOpenHelper.NAME).exists()) {
ClassicOpenHelper legacyHelper = new ClassicOpenHelper(context);
@@ -1569,6 +1575,29 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
db.execSQL("CREATE INDEX mms_date_sent_index on mms(date, address, thread_id)");
}
if (oldVersion < MESSAGE_LOG) {
db.execSQL("CREATE TABLE message_send_log (_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, " +
"group_id BLOB DEFAULT NULL)");
db.execSQL("CREATE INDEX message_log_date_sent_index ON message_send_log (date_sent)");
db.execSQL("CREATE INDEX message_log_related_message_index ON message_send_log (related_message_id, is_related_message_mms)");
db.execSQL("CREATE TRIGGER msl_sms_delete AFTER DELETE ON sms BEGIN DELETE FROM message_send_log WHERE related_message_id = old._id AND is_related_message_mms = 0; END");
db.execSQL("CREATE TRIGGER msl_mms_delete AFTER DELETE ON mms BEGIN DELETE FROM message_send_log WHERE related_message_id = old._id AND is_related_message_mms = 1; END");
db.execSQL("CREATE TABLE message_send_log_recipients (_id INTEGER PRIMARY KEY, " +
"message_send_log_id INTEGER NOT NULL REFERENCES message_send_log (_id) ON DELETE CASCADE, " +
"recipient_id INTEGER NOT NULL, " +
"device INTEGER NOT NULL)");
db.execSQL("CREATE INDEX message_send_log_recipients_recipient_index ON message_send_log_recipients (recipient_id, device)");
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

View File

@@ -0,0 +1,21 @@
package org.thoughtcrime.securesms.database.model
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.crypto.ContentHint
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
/**
* Model class for reading from the [org.thoughtcrime.securesms.database.MessageSendLogDatabase].
*/
data class MessageLogEntry(
val recipientId: RecipientId,
val dateSent: Long,
val content: SignalServiceProtos.Content,
val contentHint: ContentHint,
val relatedMessageId: Long,
val isRelatedMessageMms: Boolean,
) {
val hasRelatedMessage: Boolean
@JvmName("hasRelatedMessage")
get() = relatedMessageId > 0
}