mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Implement the message send log for sender key retries.
This commit is contained in:
committed by
Cody Henthorne
parent
6502ef64ce
commit
f19033a7a2
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>)
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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+""});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user