mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 16:49:40 +01:00
Add message editing feature.
This commit is contained in:
@@ -37,6 +37,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.signal.core.util.CursorUtil;
|
||||
import org.signal.core.util.SQLiteDatabaseExtensionsKt;
|
||||
import org.signal.core.util.SetUtil;
|
||||
import org.signal.core.util.SqlUtil;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
@@ -1484,6 +1485,16 @@ public class AttachmentTable extends DatabaseTable {
|
||||
return EncryptedMediaDataSource.createFor(attachmentSecret, dataInfo.file, dataInfo.random, dataInfo.length);
|
||||
}
|
||||
|
||||
public void duplicateAttachmentsForMessage(long destinationMessageId, long sourceMessageId) {
|
||||
SQLiteDatabaseExtensionsKt.withinTransaction(getWritableDatabase(), db -> {
|
||||
db.execSQL("CREATE TEMPORARY TABLE tmp_part AS SELECT * FROM " + TABLE_NAME + " WHERE " + MMS_ID + " = ?", SqlUtil.buildArgs(sourceMessageId));
|
||||
db.execSQL("UPDATE tmp_part SET " + ROW_ID + " = NULL, " + MMS_ID + " = ?", SqlUtil.buildArgs(destinationMessageId));
|
||||
db.execSQL("INSERT INTO " + TABLE_NAME + " SELECT * FROM tmp_part");
|
||||
db.execSQL("DROP TABLE tmp_part");
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class DataInfo {
|
||||
private final File file;
|
||||
|
||||
@@ -141,6 +141,7 @@ class DraftTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
const val QUOTE = "quote"
|
||||
const val BODY_RANGES = "mention"
|
||||
const val VOICE_NOTE = "voice_note"
|
||||
const val MESSAGE_EDIT = "message_edit"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +160,10 @@ class DraftTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
return firstOrNull { it.type == type }
|
||||
}
|
||||
|
||||
fun shouldUpdateSnippet(): Boolean {
|
||||
return none { it.type == Draft.MESSAGE_EDIT }
|
||||
}
|
||||
|
||||
fun getSnippet(context: Context): String {
|
||||
val textDraft = getDraftOfType(Draft.TEXT)
|
||||
return if (textDraft != null) {
|
||||
|
||||
@@ -134,6 +134,7 @@ import org.thoughtcrime.securesms.util.Base64
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.MessageConstraintsUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.isStory
|
||||
@@ -203,6 +204,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
const val STORY_TYPE = "story_type"
|
||||
const val PARENT_STORY_ID = "parent_story_id"
|
||||
const val SCHEDULED_DATE = "scheduled_date"
|
||||
const val LATEST_REVISION_ID = "latest_revision_id"
|
||||
const val ORIGINAL_MESSAGE_ID = "original_message_id"
|
||||
const val REVISION_NUMBER = "revision_number"
|
||||
|
||||
const val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
@@ -254,12 +258,15 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
$PARENT_STORY_ID INTEGER DEFAULT 0,
|
||||
$EXPORT_STATE BLOB DEFAULT NULL,
|
||||
$EXPORTED INTEGER DEFAULT 0,
|
||||
$SCHEDULED_DATE INTEGER DEFAULT -1
|
||||
$SCHEDULED_DATE INTEGER DEFAULT -1,
|
||||
$LATEST_REVISION_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
|
||||
$ORIGINAL_MESSAGE_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
|
||||
$REVISION_NUMBER INTEGER DEFAULT 0
|
||||
)
|
||||
"""
|
||||
|
||||
private const val INDEX_THREAD_DATE = "message_thread_date_index"
|
||||
private const val INDEX_THREAD_STORY_SCHEDULED_DATE = "message_thread_story_parent_story_scheduled_date_index"
|
||||
private const val INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID = "message_thread_story_parent_story_scheduled_date_latest_revision_id_index"
|
||||
|
||||
@JvmField
|
||||
val CREATE_INDEXS = arrayOf(
|
||||
@@ -271,8 +278,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
"CREATE INDEX IF NOT EXISTS message_reactions_unread_index ON $TABLE_NAME ($REACTIONS_UNREAD);",
|
||||
"CREATE INDEX IF NOT EXISTS message_story_type_index ON $TABLE_NAME ($STORY_TYPE);",
|
||||
"CREATE INDEX IF NOT EXISTS message_parent_story_id_index ON $TABLE_NAME ($PARENT_STORY_ID);",
|
||||
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_STORY_SCHEDULED_DATE ON $TABLE_NAME ($THREAD_ID, $DATE_RECEIVED, $STORY_TYPE, $PARENT_STORY_ID, $SCHEDULED_DATE);",
|
||||
"CREATE INDEX IF NOT EXISTS message_quote_id_quote_author_scheduled_date_index ON $TABLE_NAME ($QUOTE_ID, $QUOTE_AUTHOR, $SCHEDULED_DATE);",
|
||||
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID ON $TABLE_NAME ($THREAD_ID, $DATE_RECEIVED, $STORY_TYPE, $PARENT_STORY_ID, $SCHEDULED_DATE, $LATEST_REVISION_ID);",
|
||||
"CREATE INDEX IF NOT EXISTS message_quote_id_quote_author_scheduled_date_latest_revision_id_index ON $TABLE_NAME ($QUOTE_ID, $QUOTE_AUTHOR, $SCHEDULED_DATE, $LATEST_REVISION_ID);",
|
||||
"CREATE INDEX IF NOT EXISTS message_exported_index ON $TABLE_NAME ($EXPORTED);",
|
||||
"CREATE INDEX IF NOT EXISTS message_id_type_payment_transactions_index ON $TABLE_NAME ($ID,$TYPE) WHERE $TYPE & ${MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION} != 0;"
|
||||
)
|
||||
@@ -323,7 +330,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
MESSAGE_RANGES,
|
||||
STORY_TYPE,
|
||||
PARENT_STORY_ID,
|
||||
SCHEDULED_DATE
|
||||
SCHEDULED_DATE,
|
||||
LATEST_REVISION_ID,
|
||||
ORIGINAL_MESSAGE_ID,
|
||||
REVISION_NUMBER
|
||||
)
|
||||
|
||||
private val MMS_PROJECTION: Array<String> = MMS_PROJECTION_BASE + "NULL AS ${AttachmentTable.ATTACHMENT_JSON_ALIAS}"
|
||||
@@ -380,7 +390,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
$TYPE & ${MessageTypes.GROUP_V2_LEAVE_BITS} != ${MessageTypes.GROUP_V2_LEAVE_BITS} AND
|
||||
$STORY_TYPE = 0 AND
|
||||
$PARENT_STORY_ID <= 0 AND
|
||||
$SCHEDULED_DATE = -1 AND
|
||||
$SCHEDULED_DATE = -1 AND
|
||||
$LATEST_REVISION_ID IS NULL AND
|
||||
$TYPE NOT IN (
|
||||
${MessageTypes.PROFILE_CHANGE_TYPE},
|
||||
${MessageTypes.GV1_MIGRATION_TYPE},
|
||||
@@ -965,8 +976,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun insertMessageInbox(message: IncomingTextMessage, type: Long = MessageTypes.BASE_INBOX_TYPE): Optional<InsertResult> {
|
||||
var type = type
|
||||
fun insertMessageInbox(message: IncomingTextMessage, editedMessage: MediaMmsMessageRecord? = null): Optional<InsertResult> {
|
||||
var type = MessageTypes.BASE_INBOX_TYPE
|
||||
var tryToCollapseJoinRequestEvents = false
|
||||
|
||||
if (message.isJoined) {
|
||||
@@ -1059,13 +1070,19 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
values.put(THREAD_ID, threadId)
|
||||
values.put(SERVER_GUID, message.serverGuid)
|
||||
|
||||
if (editedMessage != null) {
|
||||
values.put(ORIGINAL_MESSAGE_ID, editedMessage.getOriginalOrOwnMessageId().id)
|
||||
} else {
|
||||
values.putNull(ORIGINAL_MESSAGE_ID)
|
||||
}
|
||||
|
||||
return if (message.isPush && isDuplicate(message, threadId)) {
|
||||
Log.w(TAG, "Duplicate message (" + message.sentTimestampMillis + "), ignoring...")
|
||||
Optional.empty()
|
||||
} else {
|
||||
val messageId = writableDatabase.insert(TABLE_NAME, null, values)
|
||||
|
||||
if (unread) {
|
||||
if (unread && editedMessage == null) {
|
||||
threads.incrementUnread(threadId, 1, 0)
|
||||
}
|
||||
|
||||
@@ -1084,6 +1101,48 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
fun insertEditMessageInbox(threadId: Long, mediaMessage: IncomingMediaMessage, targetMessage: MediaMmsMessageRecord): Optional<InsertResult> {
|
||||
val insertResult = insertSecureDecryptedMessageInbox(mediaMessage, threadId, targetMessage)
|
||||
|
||||
if (insertResult.isPresent) {
|
||||
val (messageId) = insertResult.get()
|
||||
|
||||
if (targetMessage.expireStarted > 0) {
|
||||
markExpireStarted(messageId, targetMessage.expireStarted)
|
||||
}
|
||||
|
||||
writableDatabase.update(TABLE_NAME)
|
||||
.values(LATEST_REVISION_ID to messageId)
|
||||
.where("$ID = ? OR $LATEST_REVISION_ID = ?", targetMessage.id, targetMessage.id)
|
||||
.run()
|
||||
|
||||
reactions.moveReactionsToNewMessage(newMessageId = messageId, previousId = targetMessage.id)
|
||||
}
|
||||
|
||||
return insertResult
|
||||
}
|
||||
|
||||
fun insertEditMessageInbox(textMessage: IncomingTextMessage, targetMessage: MediaMmsMessageRecord): Optional<InsertResult> {
|
||||
val insertResult = insertMessageInbox(textMessage, targetMessage)
|
||||
|
||||
if (insertResult.isPresent) {
|
||||
val (messageId) = insertResult.get()
|
||||
|
||||
if (targetMessage.expireStarted > 0) {
|
||||
markExpireStarted(messageId, targetMessage.expireStarted)
|
||||
}
|
||||
|
||||
writableDatabase.update(TABLE_NAME)
|
||||
.values(LATEST_REVISION_ID to messageId)
|
||||
.where("$ID_WHERE OR $LATEST_REVISION_ID = ?", targetMessage.id, targetMessage.id)
|
||||
.run()
|
||||
|
||||
reactions.moveReactionsToNewMessage(newMessageId = messageId, previousId = targetMessage.id)
|
||||
}
|
||||
|
||||
return insertResult
|
||||
}
|
||||
|
||||
fun insertProfileNameChangeMessages(recipient: Recipient, newProfileName: String, previousProfileName: String) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
val groupRecords = groups.getGroupsContainingMember(recipient.id, false)
|
||||
@@ -1676,7 +1735,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
fun getScheduledMessageCountForThread(threadId: Long): Int {
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE")
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID")
|
||||
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE != ?", threadId, 0, 0, -1)
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
@@ -1685,8 +1744,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
fun getMessageCountForThread(threadId: Long): Int {
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE")
|
||||
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ?", threadId, 0, 0, -1)
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID")
|
||||
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ? AND $LATEST_REVISION_ID IS NULL", threadId, 0, 0, -1)
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
}
|
||||
@@ -1694,8 +1753,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
fun getMessageCountForThread(threadId: Long, beforeTime: Long): Int {
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE")
|
||||
.where("$THREAD_ID = ? AND $DATE_RECEIVED < ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ?", threadId, beforeTime, 0, 0, -1)
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID")
|
||||
.where("$THREAD_ID = ? AND $DATE_RECEIVED < ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ? AND $LATEST_REVISION_ID IS NULL", threadId, beforeTime, 0, 0, -1)
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
}
|
||||
@@ -1746,6 +1805,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val query = """
|
||||
$THREAD_ID = ? AND
|
||||
$STORY_TYPE = 0 AND
|
||||
$LATEST_REVISION_ID IS NULL AND
|
||||
$PARENT_STORY_ID <= 0 AND
|
||||
(
|
||||
NOT $TYPE & ${MessageTypes.IGNORABLE_TYPESMASK_WHEN_COUNTING} AND
|
||||
@@ -1844,11 +1904,31 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOriginalEditedMessageRecord(messageId: Long): Long {
|
||||
return readableDatabase.select(ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$TABLE_NAME.$LATEST_REVISION_ID = ?", messageId)
|
||||
.orderBy("$ID DESC")
|
||||
.limit(1)
|
||||
.run()
|
||||
.readToSingleLong(0)
|
||||
}
|
||||
|
||||
fun getMessages(messageIds: Collection<Long?>): MmsReader {
|
||||
val ids = TextUtils.join(",", messageIds)
|
||||
return mmsReaderFor(rawQueryWithAttachments("$TABLE_NAME.$ID IN ($ids)", null))
|
||||
}
|
||||
|
||||
fun getMessageEditHistory(id: Long): MmsReader {
|
||||
val cursor = readableDatabase.select(*MMS_PROJECTION)
|
||||
.from(TABLE_NAME)
|
||||
.where("$TABLE_NAME.$ID = ? OR $TABLE_NAME.$LATEST_REVISION_ID = ?", id, id)
|
||||
.orderBy("$TABLE_NAME.$ID DESC")
|
||||
.run()
|
||||
|
||||
return mmsReaderFor(cursor)
|
||||
}
|
||||
|
||||
private fun updateMailboxBitmask(id: Long, maskOff: Long, maskOn: Long, threadId: Optional<Long>) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.execSQL(
|
||||
@@ -2378,6 +2458,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
null
|
||||
}
|
||||
|
||||
val editedMessage = getOriginalEditedMessageRecord(messageId)
|
||||
|
||||
OutgoingMessage(
|
||||
recipient = threadRecipient,
|
||||
body = body,
|
||||
@@ -2399,7 +2481,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
giftBadge = giftBadge,
|
||||
isSecure = MessageTypes.isSecureType(outboxType),
|
||||
bodyRanges = messageRanges,
|
||||
scheduledDate = scheduledDate
|
||||
scheduledDate = scheduledDate,
|
||||
messageToEdit = editedMessage
|
||||
)
|
||||
}
|
||||
} ?: throw NoSuchMessageException("No record found for id: $messageId")
|
||||
@@ -2410,7 +2493,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
retrieved: IncomingMediaMessage,
|
||||
contentLocation: String,
|
||||
candidateThreadId: Long,
|
||||
mailbox: Long
|
||||
mailbox: Long,
|
||||
editedMessage: MediaMmsMessageRecord?
|
||||
): Optional<InsertResult> {
|
||||
val threadId = if (candidateThreadId == -1L || retrieved.isGroupMessage) {
|
||||
getThreadIdFor(retrieved)
|
||||
@@ -2443,7 +2527,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
PARENT_STORY_ID to if (retrieved.parentStoryId != null) retrieved.parentStoryId.serialize() else 0,
|
||||
READ to if (silentUpdate || retrieved.isExpirationUpdate) 1 else 0,
|
||||
UNIDENTIFIED to retrieved.isUnidentified,
|
||||
SERVER_GUID to retrieved.serverGuid
|
||||
SERVER_GUID to retrieved.serverGuid,
|
||||
LATEST_REVISION_ID to null,
|
||||
ORIGINAL_MESSAGE_ID to editedMessage?.getOriginalOrOwnMessageId()?.id,
|
||||
REVISION_NUMBER to (editedMessage?.revisionNumber?.inc() ?: 0)
|
||||
)
|
||||
|
||||
val quoteAttachments: MutableList<Attachment> = mutableListOf()
|
||||
@@ -2483,6 +2570,24 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
unarchive = true
|
||||
)
|
||||
|
||||
if (editedMessage != null) {
|
||||
if (retrieved.quote != null && editedMessage.quote != null) {
|
||||
writableDatabase.execSQL(
|
||||
"""
|
||||
WITH o as (SELECT $QUOTE_ID, $QUOTE_AUTHOR, $QUOTE_BODY, $QUOTE_TYPE, $QUOTE_MISSING, $QUOTE_BODY_RANGES FROM $TABLE_NAME WHERE $ID = ${editedMessage.id})
|
||||
UPDATE $TABLE_NAME
|
||||
SET $QUOTE_ID = old.$QUOTE_ID, $QUOTE_AUTHOR = old.$QUOTE_AUTHOR, $QUOTE_BODY = old.$QUOTE_BODY, $QUOTE_TYPE = old.$QUOTE_TYPE, $QUOTE_MISSING = old.$QUOTE_MISSING, $QUOTE_BODY_RANGES = old.$QUOTE_BODY_RANGES
|
||||
FROM o old
|
||||
WHERE $TABLE_NAME.$ID = $messageId
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (retrieved.attachments.isEmpty() && editedMessage?.id != null && attachments.getAttachmentsForMessage(editedMessage.id).isNotEmpty()) {
|
||||
attachments.duplicateAttachmentsForMessage(messageId, editedMessage.id)
|
||||
}
|
||||
|
||||
val isNotStoryGroupReply = retrieved.parentStoryId == null || !retrieved.parentStoryId.isGroupReply()
|
||||
|
||||
if (!MessageTypes.isPaymentsActivated(mailbox) && !MessageTypes.isPaymentsRequestToActivate(mailbox) && !MessageTypes.isExpirationTimerUpdate(mailbox) && !retrieved.storyType.isStory && isNotStoryGroupReply) {
|
||||
@@ -2528,11 +2633,12 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
type = type or MessageTypes.SPECIAL_TYPE_PAYMENTS_ACTIVATED
|
||||
}
|
||||
|
||||
return insertMessageInbox(retrieved, contentLocation, threadId, type)
|
||||
return insertMessageInbox(retrieved, contentLocation, threadId, type, editedMessage = null)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
@Throws(MmsException::class)
|
||||
fun insertSecureDecryptedMessageInbox(retrieved: IncomingMediaMessage, threadId: Long): Optional<InsertResult> {
|
||||
fun insertSecureDecryptedMessageInbox(retrieved: IncomingMediaMessage, threadId: Long, edittedMediaMessage: MediaMmsMessageRecord? = null): Optional<InsertResult> {
|
||||
var type = MessageTypes.BASE_INBOX_TYPE or MessageTypes.SECURE_MESSAGE_BIT
|
||||
var hasSpecialType = false
|
||||
|
||||
@@ -2581,7 +2687,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
hasSpecialType = true
|
||||
}
|
||||
|
||||
return insertMessageInbox(retrieved, "", threadId, type)
|
||||
return insertMessageInbox(retrieved, "", threadId, type, edittedMediaMessage)
|
||||
}
|
||||
|
||||
fun insertMessageInbox(notification: NotificationInd, subscriptionId: Int): Pair<Long, Long> {
|
||||
@@ -2836,15 +2942,27 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
Log.w(TAG, "Found early delivery receipts for " + message.sentTimeMillis + ". Applying them.")
|
||||
}
|
||||
|
||||
var editedMessage: MessageRecord? = null
|
||||
if (message.isMessageEdit) {
|
||||
try {
|
||||
editedMessage = getMessageRecord(message.messageToEdit)
|
||||
if (!MessageConstraintsUtil.isValidEditMessageSend(editedMessage)) {
|
||||
throw MmsException("Message is not valid to edit")
|
||||
}
|
||||
} catch (e: NoSuchMessageException) {
|
||||
throw MmsException("Unable to locate edited message", e)
|
||||
}
|
||||
}
|
||||
|
||||
val contentValues = ContentValues()
|
||||
contentValues.put(DATE_SENT, message.sentTimeMillis)
|
||||
contentValues.put(MMS_MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ)
|
||||
contentValues.put(TYPE, type)
|
||||
contentValues.put(THREAD_ID, threadId)
|
||||
contentValues.put(READ, 1)
|
||||
contentValues.put(DATE_RECEIVED, System.currentTimeMillis())
|
||||
contentValues.put(DATE_RECEIVED, editedMessage?.dateReceived ?: System.currentTimeMillis())
|
||||
contentValues.put(SMS_SUBSCRIPTION_ID, message.subscriptionId)
|
||||
contentValues.put(EXPIRES_IN, message.expiresIn)
|
||||
contentValues.put(EXPIRES_IN, editedMessage?.expiresIn ?: message.expiresIn)
|
||||
contentValues.put(VIEW_ONCE, message.isViewOnce)
|
||||
contentValues.put(FROM_RECIPIENT_ID, Recipient.self().id.serialize())
|
||||
contentValues.put(FROM_DEVICE_ID, SignalStore.account().deviceId)
|
||||
@@ -2854,6 +2972,14 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
contentValues.put(STORY_TYPE, message.storyType.code)
|
||||
contentValues.put(PARENT_STORY_ID, if (message.parentStoryId != null) message.parentStoryId.serialize() else 0)
|
||||
contentValues.put(SCHEDULED_DATE, message.scheduledDate)
|
||||
contentValues.putNull(LATEST_REVISION_ID)
|
||||
|
||||
if (editedMessage != null) {
|
||||
contentValues.put(ORIGINAL_MESSAGE_ID, editedMessage.getOriginalOrOwnMessageId().id)
|
||||
contentValues.put(REVISION_NUMBER, editedMessage.revisionNumber + 1)
|
||||
} else {
|
||||
contentValues.putNull(ORIGINAL_MESSAGE_ID)
|
||||
}
|
||||
|
||||
if (message.threadRecipient.isSelf && hasAudioAttachment(message.attachments)) {
|
||||
contentValues.put(VIEWED_RECEIPT_COUNT, 1L)
|
||||
@@ -2935,10 +3061,19 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
if (message.messageToEdit > 0) {
|
||||
writableDatabase.update(TABLE_NAME)
|
||||
.values(LATEST_REVISION_ID to messageId)
|
||||
.where("$ID_WHERE OR $LATEST_REVISION_ID = ?", message.messageToEdit, message.messageToEdit)
|
||||
.run()
|
||||
|
||||
reactions.moveReactionsToNewMessage(messageId, message.messageToEdit)
|
||||
}
|
||||
|
||||
threads.updateLastSeenAndMarkSentAndLastScrolledSilenty(threadId)
|
||||
|
||||
if (!message.storyType.isStory) {
|
||||
if (message.outgoingQuote == null) {
|
||||
if (message.outgoingQuote == null && editedMessage == null) {
|
||||
ApplicationDependencies.getDatabaseObserver().notifyMessageInsertObservers(threadId, MessageId(messageId))
|
||||
} else {
|
||||
ApplicationDependencies.getDatabaseObserver().notifyConversationListeners(threadId)
|
||||
@@ -3273,6 +3408,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
reader.filterNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllRateLimitedMessageIds(): Set<Long> {
|
||||
val db = databaseHelper.signalReadableDatabase
|
||||
val where = "(" + TYPE + " & " + MessageTypes.TOTAL_MASK + " & " + MessageTypes.MESSAGE_RATE_LIMITED_BIT + ") > 0"
|
||||
@@ -3389,7 +3525,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
|
||||
private fun getMessagesInThreadAfterInclusive(threadId: Long, timestamp: Long, limit: Long): List<MessageRecord> {
|
||||
val where = "$TABLE_NAME.$THREAD_ID = ? AND $TABLE_NAME.$DATE_RECEIVED >= ? AND $TABLE_NAME.$SCHEDULED_DATE = -1"
|
||||
val where = "$TABLE_NAME.$THREAD_ID = ? AND $TABLE_NAME.$DATE_RECEIVED >= ? AND $TABLE_NAME.$SCHEDULED_DATE = -1 AND $TABLE_NAME.$LATEST_REVISION_ID IS NULL"
|
||||
val args = buildArgs(threadId, timestamp)
|
||||
|
||||
return mmsReaderFor(rawQueryWithAttachments(where, args, false, limit)).use { reader ->
|
||||
@@ -3858,7 +3994,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
fun getAllMessagesThatQuote(id: MessageId): List<MessageRecord> {
|
||||
val targetMessage: MessageRecord = getMessageRecord(id.id)
|
||||
|
||||
val query = "$QUOTE_ID = ${targetMessage.dateSent} AND $QUOTE_AUTHOR = ${targetMessage.fromRecipient.id.serialize()} AND $SCHEDULED_DATE = -1"
|
||||
val query = "$QUOTE_ID = ${targetMessage.dateSent} AND $QUOTE_AUTHOR = ${targetMessage.fromRecipient.id.serialize()} AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL"
|
||||
val order = "$DATE_RECEIVED DESC"
|
||||
|
||||
val records: MutableList<MessageRecord> = ArrayList()
|
||||
@@ -3873,8 +4009,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
|
||||
fun getQuotedMessagePosition(threadId: Long, quoteId: Long, authorId: RecipientId): Int {
|
||||
var position = 0
|
||||
readableDatabase
|
||||
.select(DATE_SENT, FROM_RECIPIENT_ID, REMOTE_DELETED)
|
||||
.select(DATE_SENT, FROM_RECIPIENT_ID, REMOTE_DELETED, LATEST_REVISION_ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1")
|
||||
.orderBy("$DATE_RECEIVED DESC")
|
||||
@@ -3887,8 +4024,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return if (cursor.requireBoolean(REMOTE_DELETED)) {
|
||||
-1
|
||||
} else {
|
||||
cursor.position
|
||||
position
|
||||
}
|
||||
} else if (cursor.requireLong(LATEST_REVISION_ID) == 0L) {
|
||||
position++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3899,7 +4038,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
readableDatabase
|
||||
.select(DATE_RECEIVED, FROM_RECIPIENT_ID, REMOTE_DELETED)
|
||||
.from(TABLE_NAME)
|
||||
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1")
|
||||
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
|
||||
.orderBy("$DATE_RECEIVED DESC")
|
||||
.run()
|
||||
.forEach { cursor ->
|
||||
@@ -3938,10 +4077,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
|
||||
if (groupStoryId > 0) {
|
||||
order = "$DATE_RECEIVED ASC"
|
||||
selection = "$THREAD_ID = $threadId AND $DATE_RECEIVED < $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID = $groupStoryId AND $SCHEDULED_DATE = -1"
|
||||
selection = "$THREAD_ID = $threadId AND $DATE_RECEIVED < $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID = $groupStoryId AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL"
|
||||
} else {
|
||||
order = "$DATE_RECEIVED DESC"
|
||||
selection = "$THREAD_ID = $threadId AND $DATE_RECEIVED > $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1"
|
||||
selection = "$THREAD_ID = $threadId AND $DATE_RECEIVED > $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL"
|
||||
}
|
||||
|
||||
return readableDatabase
|
||||
@@ -3957,7 +4096,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return readableDatabase
|
||||
.select(DATE_RECEIVED)
|
||||
.from(TABLE_NAME)
|
||||
.where("$DATE_RECEIVED > $date AND $SCHEDULED_DATE = -1")
|
||||
.where("$DATE_RECEIVED > $date AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
|
||||
.orderBy("$DATE_RECEIVED ASC")
|
||||
.limit(1)
|
||||
.run()
|
||||
@@ -3968,7 +4107,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from(TABLE_NAME)
|
||||
.where("$DATE_RECEIVED < $date AND $SCHEDULED_DATE = -1")
|
||||
.where("$DATE_RECEIVED < $date AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
}
|
||||
@@ -3986,7 +4125,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from(TABLE_NAME)
|
||||
.where("$THREAD_ID = $threadId AND $DATE_RECEIVED >= $timestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1")
|
||||
.where("$THREAD_ID = $threadId AND $DATE_RECEIVED >= $timestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
}
|
||||
@@ -4017,7 +4156,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_DATE")
|
||||
.where("$READ = 0 AND $STORY_TYPE = 0 AND $THREAD_ID = $threadId AND $PARENT_STORY_ID <= 0")
|
||||
.where("$READ = 0 AND $STORY_TYPE = 0 AND $THREAD_ID = $threadId AND $PARENT_STORY_ID <= 0 AND $LATEST_REVISION_ID IS NULL")
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
}
|
||||
@@ -4410,7 +4549,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return readableDatabase
|
||||
.select(*MMS_PROJECTION)
|
||||
.from(TABLE_NAME)
|
||||
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ?", threadId, 0, 0, -1)
|
||||
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ? AND $LATEST_REVISION_ID IS NULL", threadId, 0, 0, -1)
|
||||
.orderBy("$DATE_RECEIVED DESC")
|
||||
.limit(limitStr)
|
||||
.run()
|
||||
@@ -4422,7 +4561,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
fun getScheduledMessagesInThread(threadId: Long): List<MessageRecord> {
|
||||
val cursor = readableDatabase
|
||||
.select(*MMS_PROJECTION)
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE")
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID")
|
||||
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE != ?", threadId, 0, 0, -1)
|
||||
.orderBy("$SCHEDULED_DATE DESC, $ID DESC")
|
||||
.run()
|
||||
@@ -4690,6 +4829,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageRecord.getOriginalOrOwnMessageId(): MessageId {
|
||||
return this.originalMessageId ?: MessageId(this.id)
|
||||
}
|
||||
|
||||
protected enum class ReceiptType(val columnName: String, val groupStatus: Int) {
|
||||
READ(READ_RECEIPT_COUNT, GroupReceiptTable.STATUS_READ),
|
||||
DELIVERY(DELIVERY_RECEIPT_COUNT, GroupReceiptTable.STATUS_DELIVERED),
|
||||
@@ -4974,6 +5117,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val storyType = StoryType.fromCode(cursor.requireInt(STORY_TYPE))
|
||||
val parentStoryId = ParentStoryId.deserialize(cursor.requireLong(PARENT_STORY_ID))
|
||||
val scheduledDate = cursor.requireLong(SCHEDULED_DATE)
|
||||
val latestRevisionId: MessageId? = cursor.requireLong(LATEST_REVISION_ID).let { if (it == 0L) null else MessageId(it) }
|
||||
val originalMessageId: MessageId? = cursor.requireLong(ORIGINAL_MESSAGE_ID).let { if (it == 0L) null else MessageId(it) }
|
||||
val editCount = cursor.requireInt(REVISION_NUMBER)
|
||||
|
||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
readReceiptCount = 0
|
||||
@@ -5057,7 +5203,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
giftBadge,
|
||||
null,
|
||||
null,
|
||||
scheduledDate
|
||||
scheduledDate,
|
||||
latestRevisionId,
|
||||
originalMessageId,
|
||||
editCount
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.database.Cursor
|
||||
import org.signal.core.util.CursorUtil
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.delete
|
||||
import org.signal.core.util.update
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
@@ -177,4 +178,12 @@ class ReactionTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
.where("$MESSAGE_ID NOT IN (SELECT ${MessageTable.ID} FROM ${MessageTable.TABLE_NAME})")
|
||||
.run()
|
||||
}
|
||||
|
||||
fun moveReactionsToNewMessage(newMessageId: Long, previousId: Long) {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(MESSAGE_ID to newMessageId)
|
||||
.where("$MESSAGE_ID = ?", previousId)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,9 @@ class SearchTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
WHERE
|
||||
$FTS_TABLE_NAME MATCH ? AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.TYPE} & ${MessageTypes.GROUP_V2_BIT} = 0 AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.TYPE} & ${MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION} = 0
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.TYPE} & ${MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION} = 0 AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.SCHEDULED_DATE} < 0 AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.LATEST_REVISION_ID} IS NULL
|
||||
ORDER BY ${MessageTable.DATE_RECEIVED} DESC
|
||||
LIMIT 500
|
||||
"""
|
||||
@@ -99,7 +101,11 @@ class SearchTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
INNER JOIN ${ThreadTable.TABLE_NAME} ON $FTS_TABLE_NAME.$THREAD_ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.ID}
|
||||
WHERE
|
||||
$FTS_TABLE_NAME MATCH ? AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} = ?
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} = ? AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.TYPE} & ${MessageTypes.GROUP_V2_BIT} = 0 AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.TYPE} & ${MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION} = 0 AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.SCHEDULED_DATE} < 0 AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.LATEST_REVISION_ID} IS NULL
|
||||
ORDER BY ${MessageTable.DATE_RECEIVED} DESC
|
||||
LIMIT 500
|
||||
"""
|
||||
|
||||
@@ -161,7 +161,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
||||
|
||||
Log.i(TAG, "Upgrading database: $oldVersion, $newVersion")
|
||||
val startTime = System.currentTimeMillis()
|
||||
db.setForeignKeyConstraintsEnabled(false)
|
||||
db.beginTransaction()
|
||||
try {
|
||||
migrate(context, db, oldVersion, newVersion)
|
||||
@@ -169,7 +168,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
||||
db.setTransactionSuccessful()
|
||||
} finally {
|
||||
db.endTransaction()
|
||||
db.setForeignKeyConstraintsEnabled(true)
|
||||
|
||||
// We have to re-begin the transaction for the calling code (see comment at start of method)
|
||||
db.beginTransaction()
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V182_CallTableMigra
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V183_CallLinkTableMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V184_CallLinkReplaceIndexMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V185_MessageRecipientsMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V186_AddEditMessageColumnsMigration
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
@@ -49,7 +50,7 @@ object SignalDatabaseMigrations {
|
||||
|
||||
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
|
||||
|
||||
const val DATABASE_VERSION = 185
|
||||
const val DATABASE_VERSION = 186
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
@@ -200,6 +201,10 @@ object SignalDatabaseMigrations {
|
||||
if (oldVersion < 185) {
|
||||
V185_MessageRecipientsMigration.migrate(context, db, oldVersion, newVersion)
|
||||
}
|
||||
|
||||
if (oldVersion < 186) {
|
||||
V186_AddEditMessageColumnsMigration.migrate(context, db, oldVersion, newVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
import org.signal.core.util.Stopwatch
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.requireNonNullString
|
||||
|
||||
/**
|
||||
* Changes needed for edit message. New foreign keys require recreating the table.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V186_AddEditMessageColumnsMigration : SignalDatabaseMigration {
|
||||
|
||||
private val TAG = Log.tag(V186_AddEditMessageColumnsMigration::class.java)
|
||||
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
val stopwatch = Stopwatch("migration")
|
||||
|
||||
val dependentItems: List<SqlItem> = getAllDependentItems(db, "message")
|
||||
dependentItems.forEach { item ->
|
||||
val sql = "DROP ${item.type} IF EXISTS ${item.name}"
|
||||
Log.d(TAG, "Executing: $sql")
|
||||
db.execSQL(sql)
|
||||
}
|
||||
|
||||
stopwatch.split("drop-dependents")
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TABLE message_tmp (
|
||||
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
date_sent INTEGER NOT NULL,
|
||||
date_received INTEGER NOT NULL,
|
||||
date_server INTEGER DEFAULT -1,
|
||||
thread_id INTEGER NOT NULL REFERENCES thread (_id) ON DELETE CASCADE,
|
||||
from_recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
|
||||
from_device_id INTEGER,
|
||||
to_recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
|
||||
type INTEGER NOT NULL,
|
||||
body TEXT,
|
||||
read INTEGER DEFAULT 0,
|
||||
ct_l TEXT,
|
||||
exp INTEGER,
|
||||
m_type INTEGER,
|
||||
m_size INTEGER,
|
||||
st INTEGER,
|
||||
tr_id TEXT,
|
||||
subscription_id INTEGER DEFAULT -1,
|
||||
receipt_timestamp INTEGER DEFAULT -1,
|
||||
delivery_receipt_count INTEGER DEFAULT 0,
|
||||
read_receipt_count INTEGER DEFAULT 0,
|
||||
viewed_receipt_count INTEGER DEFAULT 0,
|
||||
mismatched_identities TEXT DEFAULT NULL,
|
||||
network_failures TEXT DEFAULT NULL,
|
||||
expires_in INTEGER DEFAULT 0,
|
||||
expire_started INTEGER DEFAULT 0,
|
||||
notified INTEGER DEFAULT 0,
|
||||
quote_id INTEGER DEFAULT 0,
|
||||
quote_author INTEGER DEFAULT 0,
|
||||
quote_body TEXT DEFAULT NULL,
|
||||
quote_missing INTEGER DEFAULT 0,
|
||||
quote_mentions BLOB DEFAULT NULL,
|
||||
quote_type INTEGER DEFAULT 0,
|
||||
shared_contacts TEXT DEFAULT NULL,
|
||||
unidentified INTEGER DEFAULT 0,
|
||||
link_previews TEXT DEFAULT NULL,
|
||||
view_once INTEGER DEFAULT 0,
|
||||
reactions_unread INTEGER DEFAULT 0,
|
||||
reactions_last_seen INTEGER DEFAULT -1,
|
||||
remote_deleted INTEGER DEFAULT 0,
|
||||
mentions_self INTEGER DEFAULT 0,
|
||||
notified_timestamp INTEGER DEFAULT 0,
|
||||
server_guid TEXT DEFAULT NULL,
|
||||
message_ranges BLOB DEFAULT NULL,
|
||||
story_type INTEGER DEFAULT 0,
|
||||
parent_story_id INTEGER DEFAULT 0,
|
||||
export_state BLOB DEFAULT NULL,
|
||||
exported INTEGER DEFAULT 0,
|
||||
scheduled_date INTEGER DEFAULT -1,
|
||||
latest_revision_id INTEGER DEFAULT NULL REFERENCES message (_id) ON DELETE CASCADE,
|
||||
original_message_id INTEGER DEFAULT NULL REFERENCES message (_id) ON DELETE CASCADE,
|
||||
revision_number INTEGER DEFAULT 0
|
||||
)
|
||||
"""
|
||||
)
|
||||
stopwatch.split("create-table")
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
INSERT INTO message_tmp
|
||||
SELECT
|
||||
_id,
|
||||
date_sent,
|
||||
date_received,
|
||||
date_server,
|
||||
thread_id,
|
||||
from_recipient_id,
|
||||
from_device_id,
|
||||
to_recipient_id,
|
||||
type,
|
||||
body,
|
||||
read,
|
||||
ct_l,
|
||||
exp,
|
||||
m_type,
|
||||
m_size,
|
||||
st,
|
||||
tr_id,
|
||||
subscription_id,
|
||||
receipt_timestamp,
|
||||
delivery_receipt_count,
|
||||
read_receipt_count,
|
||||
viewed_receipt_count,
|
||||
mismatched_identities,
|
||||
network_failures,
|
||||
expires_in,
|
||||
expire_started,
|
||||
notified,
|
||||
quote_id,
|
||||
quote_author,
|
||||
quote_body,
|
||||
quote_missing,
|
||||
quote_mentions,
|
||||
quote_type,
|
||||
shared_contacts,
|
||||
unidentified,
|
||||
link_previews,
|
||||
view_once,
|
||||
reactions_unread,
|
||||
reactions_last_seen,
|
||||
remote_deleted,
|
||||
mentions_self,
|
||||
notified_timestamp,
|
||||
server_guid,
|
||||
message_ranges,
|
||||
story_type,
|
||||
parent_story_id,
|
||||
export_state,
|
||||
exported,
|
||||
scheduled_date,
|
||||
NULL AS latest_revision_id,
|
||||
NULL AS original_message_id,
|
||||
0 as revision_number
|
||||
FROM message
|
||||
"""
|
||||
)
|
||||
stopwatch.split("copy-data")
|
||||
|
||||
db.execSQL("DROP TABLE message")
|
||||
stopwatch.split("drop-old")
|
||||
|
||||
db.execSQL("ALTER TABLE message_tmp RENAME TO message")
|
||||
stopwatch.split("rename-table")
|
||||
|
||||
dependentItems.forEach { item ->
|
||||
val sql = when (item.name) {
|
||||
"message_thread_story_parent_story_scheduled_date_index" -> "CREATE INDEX message_thread_story_parent_story_scheduled_date_latest_revision_id_index ON message (thread_id, date_received, story_type, parent_story_id, scheduled_date, latest_revision_id)"
|
||||
"message_quote_id_quote_author_scheduled_date_index" -> "CREATE INDEX message_quote_id_quote_author_scheduled_date_latest_revision_id_index ON message (quote_id, quote_author, scheduled_date, latest_revision_id)"
|
||||
else -> item.createStatement
|
||||
}
|
||||
Log.d(TAG, "Executing: $sql")
|
||||
db.execSQL(sql)
|
||||
}
|
||||
stopwatch.split("recreate-dependents")
|
||||
|
||||
db.execSQL("PRAGMA foreign_key_check")
|
||||
stopwatch.split("fk-check")
|
||||
|
||||
stopwatch.stop(TAG)
|
||||
}
|
||||
|
||||
private fun getAllDependentItems(db: SQLiteDatabase, tableName: String): List<SqlItem> {
|
||||
return db.rawQuery("SELECT type, name, sql FROM sqlite_schema WHERE tbl_name='$tableName' AND type != 'table'").readToList { cursor ->
|
||||
SqlItem(
|
||||
type = cursor.requireNonNullString("type"),
|
||||
name = cursor.requireNonNullString("name"),
|
||||
createStatement = cursor.requireNonNullString("sql")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class SqlItem(
|
||||
val type: String,
|
||||
val name: String,
|
||||
val createStatement: String
|
||||
)
|
||||
}
|
||||
@@ -54,7 +54,9 @@ public class InMemoryMessageRecord extends MessageRecord {
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
-1);
|
||||
-1,
|
||||
null,
|
||||
0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -70,6 +70,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
private final Payment payment;
|
||||
private final CallTable.Call call;
|
||||
private final long scheduledDate;
|
||||
private final MessageId latestRevisionId;
|
||||
|
||||
public MediaMmsMessageRecord(long id,
|
||||
Recipient fromRecipient,
|
||||
@@ -106,18 +107,22 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
@Nullable GiftBadge giftBadge,
|
||||
@Nullable Payment payment,
|
||||
@Nullable CallTable.Call call,
|
||||
long scheduledDate)
|
||||
long scheduledDate,
|
||||
@Nullable MessageId latestRevisionId,
|
||||
@Nullable MessageId originalMessageId,
|
||||
int revisionNumber)
|
||||
{
|
||||
super(id, body, fromRecipient, fromDeviceId, toRecipient, dateSent,
|
||||
dateReceived, dateServer, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
|
||||
subscriptionId, expiresIn, expireStarted, viewOnce, slideDeck,
|
||||
readReceiptCount, quote, contacts, linkPreviews, unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp,
|
||||
storyType, parentStoryId, giftBadge);
|
||||
this.mentionsSelf = mentionsSelf;
|
||||
this.messageRanges = messageRanges;
|
||||
this.payment = payment;
|
||||
this.call = call;
|
||||
this.scheduledDate = scheduledDate;
|
||||
storyType, parentStoryId, giftBadge, originalMessageId, revisionNumber);
|
||||
this.mentionsSelf = mentionsSelf;
|
||||
this.messageRanges = messageRanges;
|
||||
this.payment = payment;
|
||||
this.call = call;
|
||||
this.scheduledDate = scheduledDate;
|
||||
this.latestRevisionId = latestRevisionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -204,18 +209,24 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
return scheduledDate;
|
||||
}
|
||||
|
||||
public @Nullable MessageId getLatestRevisionId() {
|
||||
return latestRevisionId;
|
||||
}
|
||||
|
||||
public @NonNull MediaMmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
|
||||
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
||||
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate());
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber());
|
||||
}
|
||||
|
||||
public @NonNull MediaMmsMessageRecord withoutQuote() {
|
||||
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
||||
getReadReceiptCount(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate());
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber());
|
||||
}
|
||||
|
||||
public @NonNull MediaMmsMessageRecord withAttachments(@NonNull Context context, @NonNull List<DatabaseAttachment> attachments) {
|
||||
@@ -236,14 +247,16 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), slideDeck,
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
||||
getReadReceiptCount(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate());
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber());
|
||||
}
|
||||
|
||||
public @NonNull MediaMmsMessageRecord withPayment(@NonNull Payment payment) {
|
||||
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
||||
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getScheduledDate());
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber());
|
||||
}
|
||||
|
||||
|
||||
@@ -251,7 +264,8 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
|
||||
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getScheduledDate());
|
||||
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber());
|
||||
}
|
||||
|
||||
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {
|
||||
|
||||
@@ -102,6 +102,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
private final boolean remoteDelete;
|
||||
private final long notifiedTimestamp;
|
||||
private final long receiptTimestamp;
|
||||
private final MessageId originalMessageId;
|
||||
private final int revisionNumber;
|
||||
|
||||
protected Boolean isJumboji = null;
|
||||
|
||||
@@ -110,10 +112,18 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
int deliveryStatus, int deliveryReceiptCount, long type,
|
||||
Set<IdentityKeyMismatch> mismatches,
|
||||
Set<NetworkFailure> networkFailures,
|
||||
int subscriptionId, long expiresIn, long expireStarted,
|
||||
int readReceiptCount, boolean unidentified,
|
||||
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp,
|
||||
int viewedReceiptCount, long receiptTimestamp)
|
||||
int subscriptionId,
|
||||
long expiresIn,
|
||||
long expireStarted,
|
||||
int readReceiptCount,
|
||||
boolean unidentified,
|
||||
@NonNull List<ReactionRecord> reactions,
|
||||
boolean remoteDelete,
|
||||
long notifiedTimestamp,
|
||||
int viewedReceiptCount,
|
||||
long receiptTimestamp,
|
||||
@Nullable MessageId originalMessageId,
|
||||
int revisionNumber)
|
||||
{
|
||||
super(body, fromRecipient, toRecipient, dateSent, dateReceived,
|
||||
threadId, deliveryStatus, deliveryReceiptCount, type,
|
||||
@@ -131,6 +141,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
this.remoteDelete = remoteDelete;
|
||||
this.notifiedTimestamp = notifiedTimestamp;
|
||||
this.receiptTimestamp = receiptTimestamp;
|
||||
this.originalMessageId = originalMessageId;
|
||||
this.revisionNumber = revisionNumber;
|
||||
}
|
||||
|
||||
public abstract boolean isMms();
|
||||
@@ -721,6 +733,18 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
|
||||
public boolean isEditMessage() {
|
||||
return originalMessageId != null;
|
||||
}
|
||||
|
||||
public @Nullable MessageId getOriginalMessageId() {
|
||||
return originalMessageId;
|
||||
}
|
||||
|
||||
public int getRevisionNumber() {
|
||||
return revisionNumber;
|
||||
}
|
||||
|
||||
public static final class InviteAddState {
|
||||
|
||||
private final boolean invited;
|
||||
|
||||
@@ -39,12 +39,13 @@ public abstract class MmsMessageRecord extends MessageRecord {
|
||||
@NonNull List<LinkPreview> linkPreviews, boolean unidentified,
|
||||
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp,
|
||||
int viewedReceiptCount, long receiptTimestamp, @NonNull StoryType storyType,
|
||||
@Nullable ParentStoryId parentStoryId, @Nullable GiftBadge giftBadge)
|
||||
@Nullable ParentStoryId parentStoryId, @Nullable GiftBadge giftBadge, @Nullable MessageId originalMessageId,
|
||||
int revisionNumber)
|
||||
{
|
||||
super(id, body, fromRecipient, fromDeviceId, toRecipient,
|
||||
dateSent, dateReceived, dateServer, threadId, deliveryStatus, deliveryReceiptCount,
|
||||
type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount,
|
||||
unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp);
|
||||
unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp, originalMessageId, revisionNumber);
|
||||
|
||||
this.slideDeck = slideDeck;
|
||||
this.quote = quote;
|
||||
|
||||
@@ -60,7 +60,7 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
|
||||
dateSent, dateReceived, -1, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
|
||||
new HashSet<>(), new HashSet<>(), subscriptionId,
|
||||
0, 0, false, slideDeck, readReceiptCount, null, Collections.emptyList(), Collections.emptyList(), false,
|
||||
Collections.emptyList(), false, 0, viewedReceiptCount, receiptTimestamp, storyType, parentStoryId, giftBadge);
|
||||
Collections.emptyList(), false, 0, viewedReceiptCount, receiptTimestamp, storyType, parentStoryId, giftBadge, null, 0);
|
||||
|
||||
this.contentLocation = contentLocation;
|
||||
this.messageSize = messageSize;
|
||||
|
||||
Reference in New Issue
Block a user