Fix handling of early receipts.
We were storing the early content under the wrong recipient.
This commit is contained in:
parent
370c2b941c
commit
a42c3d7ce8
5 changed files with 74 additions and 112 deletions
|
@ -45,6 +45,7 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -128,7 +129,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
|
||||||
public abstract void markGiftRedemptionFailed(long messageId);
|
public abstract void markGiftRedemptionFailed(long messageId);
|
||||||
|
|
||||||
public abstract Set<MessageUpdate> incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, boolean storiesOnly);
|
public abstract Set<MessageUpdate> incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, boolean storiesOnly);
|
||||||
abstract @NonNull MmsSmsDatabase.TimestampReadResult setTimestampRead(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead);
|
|
||||||
public abstract List<MarkedMessageInfo> setEntireThreadRead(long threadId);
|
public abstract List<MarkedMessageInfo> setEntireThreadRead(long threadId);
|
||||||
public abstract List<MarkedMessageInfo> setMessagesReadSince(long threadId, long timestamp);
|
public abstract List<MarkedMessageInfo> setMessagesReadSince(long threadId, long timestamp);
|
||||||
public abstract List<MarkedMessageInfo> setAllMessagesRead();
|
public abstract List<MarkedMessageInfo> setAllMessagesRead();
|
||||||
|
@ -276,6 +277,50 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a synchronized read message.
|
||||||
|
* @param messageId An id representing the author-timestamp pair of the message that was read on a linked device. Note that the author could be self when
|
||||||
|
* syncing read receipts for reactions.
|
||||||
|
*/
|
||||||
|
final @NonNull MmsSmsDatabase.TimestampReadResult setTimestampReadFromSyncMessage(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
|
||||||
|
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||||
|
List<Pair<Long, Long>> expiring = new LinkedList<>();
|
||||||
|
String[] projection = new String[] { ID, THREAD_ID, EXPIRES_IN, EXPIRE_STARTED };
|
||||||
|
String query = getDateSentColumnName() + " = ? AND (" + RECIPIENT_ID + " = ? OR (" + RECIPIENT_ID + " = ? AND " + getOutgoingTypeClause() + "))";
|
||||||
|
String[] args = SqlUtil.buildArgs(messageId.getTimetamp(), messageId.getRecipientId(), Recipient.self().getId());
|
||||||
|
List<Long> threads = new LinkedList<>();
|
||||||
|
|
||||||
|
try (Cursor cursor = database.query(getTableName(), projection, query, args, null, null, null)) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||||
|
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
|
||||||
|
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
|
||||||
|
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED));
|
||||||
|
|
||||||
|
expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted;
|
||||||
|
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(READ, 1);
|
||||||
|
values.put(REACTIONS_UNREAD, 0);
|
||||||
|
values.put(REACTIONS_LAST_SEEN, System.currentTimeMillis());
|
||||||
|
|
||||||
|
if (expiresIn > 0) {
|
||||||
|
values.put(EXPIRE_STARTED, expireStarted);
|
||||||
|
expiring.add(new Pair<>(id, expiresIn));
|
||||||
|
}
|
||||||
|
|
||||||
|
database.update(getTableName(), values, ID_WHERE, SqlUtil.buildArgs(id));
|
||||||
|
|
||||||
|
threads.add(threadId);
|
||||||
|
|
||||||
|
Long latest = threadToLatestRead.get(threadId);
|
||||||
|
threadToLatestRead.put(threadId, (latest != null) ? Math.max(latest, messageId.getTimetamp()) : messageId.getTimetamp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MmsSmsDatabase.TimestampReadResult(expiring, threads);
|
||||||
|
}
|
||||||
|
|
||||||
private int getMessageCountForRecipientsAndType(String typeClause) {
|
private int getMessageCountForRecipientsAndType(String typeClause) {
|
||||||
|
|
||||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||||
|
|
|
@ -1538,51 +1538,6 @@ public class MmsDatabase extends MessageDatabase {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull MmsSmsDatabase.TimestampReadResult setTimestampRead(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
|
|
||||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
|
||||||
List<Pair<Long, Long>> expiring = new LinkedList<>();
|
|
||||||
String[] projection = new String[] { ID, THREAD_ID, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED, RECIPIENT_ID };
|
|
||||||
String query = DATE_SENT + " = ?";
|
|
||||||
String[] args = SqlUtil.buildArgs(messageId.getTimetamp());
|
|
||||||
List<Long> threads = new LinkedList<>();
|
|
||||||
|
|
||||||
try (Cursor cursor = database.query(TABLE_NAME, projection, query, args, null, null, null)) {
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
RecipientId theirRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID)));
|
|
||||||
RecipientId ourRecipientId = messageId.getRecipientId();
|
|
||||||
|
|
||||||
if (ourRecipientId.equals(theirRecipientId) || Recipient.resolved(theirRecipientId).isGroup() || ourRecipientId.equals(Recipient.self().getId())) {
|
|
||||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
|
||||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
|
|
||||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
|
|
||||||
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED));
|
|
||||||
|
|
||||||
expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted;
|
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(READ, 1);
|
|
||||||
values.put(REACTIONS_UNREAD, 0);
|
|
||||||
values.put(REACTIONS_LAST_SEEN, System.currentTimeMillis());
|
|
||||||
|
|
||||||
if (expiresIn > 0) {
|
|
||||||
values.put(EXPIRE_STARTED, expireStarted);
|
|
||||||
expiring.add(new Pair<>(id, expiresIn));
|
|
||||||
}
|
|
||||||
|
|
||||||
database.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(id));
|
|
||||||
|
|
||||||
threads.add(threadId);
|
|
||||||
|
|
||||||
Long latest = threadToLatestRead.get(threadId);
|
|
||||||
threadToLatestRead.put(threadId, (latest != null) ? Math.max(latest, messageId.getTimetamp()) : messageId.getTimetamp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MmsSmsDatabase.TimestampReadResult(expiring, threads);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Pair<RecipientId, Long> getOldestUnreadMentionDetails(long threadId) {
|
public @Nullable Pair<RecipientId, Long> getOldestUnreadMentionDetails(long threadId) {
|
||||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||||
|
|
|
@ -45,7 +45,6 @@ import java.io.Closeable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -617,7 +616,7 @@ public class MmsSmsDatabase extends Database {
|
||||||
/**
|
/**
|
||||||
* @return Unhandled ids
|
* @return Unhandled ids
|
||||||
*/
|
*/
|
||||||
public Collection<SyncMessageId> setTimestampRead(@NonNull Recipient senderRecipient, @NonNull List<ReadMessage> readMessages, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
|
public Collection<SyncMessageId> setTimestampReadFromSyncMessage(@NonNull List<ReadMessage> readMessages, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
|
|
||||||
List<Pair<Long, Long>> expiringText = new LinkedList<>();
|
List<Pair<Long, Long>> expiringText = new LinkedList<>();
|
||||||
|
@ -628,10 +627,11 @@ public class MmsSmsDatabase extends Database {
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
try {
|
try {
|
||||||
for (ReadMessage readMessage : readMessages) {
|
for (ReadMessage readMessage : readMessages) {
|
||||||
TimestampReadResult textResult = SignalDatabase.sms().setTimestampRead(new SyncMessageId(senderRecipient.getId(), readMessage.getTimestamp()),
|
RecipientId authorId = Recipient.externalPush(readMessage.getSender()).getId();
|
||||||
|
TimestampReadResult textResult = SignalDatabase.sms().setTimestampReadFromSyncMessage(new SyncMessageId(authorId, readMessage.getTimestamp()),
|
||||||
proposedExpireStarted,
|
proposedExpireStarted,
|
||||||
threadToLatestRead);
|
threadToLatestRead);
|
||||||
TimestampReadResult mediaResult = SignalDatabase.mms().setTimestampRead(new SyncMessageId(senderRecipient.getId(), readMessage.getTimestamp()),
|
TimestampReadResult mediaResult = SignalDatabase.mms().setTimestampReadFromSyncMessage(new SyncMessageId(authorId, readMessage.getTimestamp()),
|
||||||
proposedExpireStarted,
|
proposedExpireStarted,
|
||||||
threadToLatestRead);
|
threadToLatestRead);
|
||||||
|
|
||||||
|
@ -642,7 +642,7 @@ public class MmsSmsDatabase extends Database {
|
||||||
updatedThreads.addAll(mediaResult.threads);
|
updatedThreads.addAll(mediaResult.threads);
|
||||||
|
|
||||||
if (textResult.threads.isEmpty() && mediaResult.threads.isEmpty()) {
|
if (textResult.threads.isEmpty() && mediaResult.threads.isEmpty()) {
|
||||||
unhandled.add(new SyncMessageId(senderRecipient.getId(), readMessage.getTimestamp()));
|
unhandled.add(new SyncMessageId(authorId, readMessage.getTimestamp()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -548,51 +548,6 @@ public class SmsDatabase extends MessageDatabase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull MmsSmsDatabase.TimestampReadResult setTimestampRead(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead) {
|
|
||||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
|
||||||
List<Pair<Long, Long>> expiring = new LinkedList<>();
|
|
||||||
String[] projection = new String[] {ID, THREAD_ID, RECIPIENT_ID, TYPE, EXPIRES_IN, EXPIRE_STARTED};
|
|
||||||
String query = DATE_SENT + " = ?";
|
|
||||||
String[] args = SqlUtil.buildArgs(messageId.getTimetamp());
|
|
||||||
List<Long> threads = new LinkedList<>();
|
|
||||||
|
|
||||||
try (Cursor cursor = database.query(TABLE_NAME, projection, query, args, null, null, null)) {
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
RecipientId theirRecipientId = messageId.getRecipientId();
|
|
||||||
RecipientId outRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID)));
|
|
||||||
|
|
||||||
if (outRecipientId.equals(theirRecipientId) || theirRecipientId.equals(Recipient.self().getId())) {
|
|
||||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
|
||||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
|
|
||||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
|
|
||||||
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED));
|
|
||||||
|
|
||||||
expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted;
|
|
||||||
|
|
||||||
ContentValues contentValues = new ContentValues();
|
|
||||||
contentValues.put(READ, 1);
|
|
||||||
contentValues.put(REACTIONS_UNREAD, 0);
|
|
||||||
contentValues.put(REACTIONS_LAST_SEEN, System.currentTimeMillis());
|
|
||||||
|
|
||||||
if (expiresIn > 0) {
|
|
||||||
contentValues.put(EXPIRE_STARTED, expireStarted);
|
|
||||||
expiring.add(new Pair<>(id, expiresIn));
|
|
||||||
}
|
|
||||||
|
|
||||||
database.update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(id));
|
|
||||||
|
|
||||||
threads.add(threadId);
|
|
||||||
|
|
||||||
Long latest = threadToLatestRead.get(threadId);
|
|
||||||
threadToLatestRead.put(threadId, (latest != null) ? Math.max(latest, messageId.getTimetamp()) : messageId.getTimetamp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MmsSmsDatabase.TimestampReadResult(expiring, threads);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<MarkedMessageInfo> setEntireThreadRead(long threadId) {
|
public List<MarkedMessageInfo> setEntireThreadRead(long threadId) {
|
||||||
return setMessagesRead(THREAD_ID + " = ?", new String[] {String.valueOf(threadId)});
|
return setMessagesRead(THREAD_ID + " = ?", new String[] {String.valueOf(threadId)});
|
||||||
|
|
|
@ -338,7 +338,7 @@ public final class MessageContentProcessor {
|
||||||
|
|
||||||
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get(), senderRecipient);
|
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get(), senderRecipient);
|
||||||
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get(), content.getTimestamp());
|
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get(), content.getTimestamp());
|
||||||
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(content, syncMessage.getRead().get(), content.getTimestamp(), senderRecipient);
|
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(content, syncMessage.getRead().get(), content.getTimestamp());
|
||||||
else if (syncMessage.getViewed().isPresent()) handleSynchronizeViewedMessage(syncMessage.getViewed().get(), content.getTimestamp());
|
else if (syncMessage.getViewed().isPresent()) handleSynchronizeViewedMessage(syncMessage.getViewed().get(), content.getTimestamp());
|
||||||
else if (syncMessage.getViewOnceOpen().isPresent()) handleSynchronizeViewOnceOpenMessage(content, syncMessage.getViewOnceOpen().get(), content.getTimestamp());
|
else if (syncMessage.getViewOnceOpen().isPresent()) handleSynchronizeViewOnceOpenMessage(content, syncMessage.getViewOnceOpen().get(), content.getTimestamp());
|
||||||
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
|
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
|
||||||
|
@ -1279,14 +1279,13 @@ public final class MessageContentProcessor {
|
||||||
|
|
||||||
private void handleSynchronizeReadMessage(@NonNull SignalServiceContent content,
|
private void handleSynchronizeReadMessage(@NonNull SignalServiceContent content,
|
||||||
@NonNull List<ReadMessage> readMessages,
|
@NonNull List<ReadMessage> readMessages,
|
||||||
long envelopeTimestamp,
|
long envelopeTimestamp)
|
||||||
@NonNull Recipient senderRecipient)
|
|
||||||
{
|
{
|
||||||
log(envelopeTimestamp, "Synchronize read message. Count: " + readMessages.size() + ", Timestamps: " + Stream.of(readMessages).map(ReadMessage::getTimestamp).toList());
|
log(envelopeTimestamp, "Synchronize read message. Count: " + readMessages.size() + ", Timestamps: " + Stream.of(readMessages).map(ReadMessage::getTimestamp).toList());
|
||||||
|
|
||||||
Map<Long, Long> threadToLatestRead = new HashMap<>();
|
Map<Long, Long> threadToLatestRead = new HashMap<>();
|
||||||
|
|
||||||
Collection<SyncMessageId> unhandled = SignalDatabase.mmsSms().setTimestampRead(senderRecipient, readMessages, envelopeTimestamp, threadToLatestRead);
|
Collection<SyncMessageId> unhandled = SignalDatabase.mmsSms().setTimestampReadFromSyncMessage(readMessages, envelopeTimestamp, threadToLatestRead);
|
||||||
|
|
||||||
List<MessageDatabase.MarkedMessageInfo> markedMessages = SignalDatabase.threads().setReadSince(threadToLatestRead, false);
|
List<MessageDatabase.MarkedMessageInfo> markedMessages = SignalDatabase.threads().setReadSince(threadToLatestRead, false);
|
||||||
|
|
||||||
|
@ -2521,10 +2520,14 @@ public final class MessageContentProcessor {
|
||||||
|
|
||||||
SignalDatabase.mmsSms().updateViewedStories(handled);
|
SignalDatabase.mmsSms().updateViewedStories(handled);
|
||||||
|
|
||||||
|
if (unhandled.size() > 0) {
|
||||||
|
RecipientId selfId = Recipient.self().getId();
|
||||||
|
|
||||||
for (SyncMessageId id : unhandled) {
|
for (SyncMessageId id : unhandled) {
|
||||||
warn(String.valueOf(content.getTimestamp()), "[handleViewedReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + " author: " + id.getRecipientId());
|
warn(String.valueOf(content.getTimestamp()), "[handleViewedReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + ", author: " + id.getRecipientId() + " | Receipt so associating with message from self (" + selfId + ")");
|
||||||
if (!processingEarlyContent) {
|
if (!processingEarlyContent) {
|
||||||
ApplicationDependencies.getEarlyMessageCache().store(id.getRecipientId(), id.getTimetamp(), content);
|
ApplicationDependencies.getEarlyMessageCache().store(selfId, id.getTimetamp(), content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2576,10 +2579,14 @@ public final class MessageContentProcessor {
|
||||||
|
|
||||||
Collection<SyncMessageId> unhandled = SignalDatabase.mmsSms().incrementReadReceiptCounts(ids, content.getTimestamp());
|
Collection<SyncMessageId> unhandled = SignalDatabase.mmsSms().incrementReadReceiptCounts(ids, content.getTimestamp());
|
||||||
|
|
||||||
|
if (unhandled.size() > 0) {
|
||||||
|
RecipientId selfId = Recipient.self().getId();
|
||||||
|
|
||||||
for (SyncMessageId id : unhandled) {
|
for (SyncMessageId id : unhandled) {
|
||||||
warn(String.valueOf(content.getTimestamp()), "[handleReadReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + " author: " + id.getRecipientId());
|
warn(String.valueOf(content.getTimestamp()), "[handleReadReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + ", author: " + id.getRecipientId() + " | Receipt, so associating with message from self (" + selfId + ")");
|
||||||
if (!processingEarlyContent) {
|
if (!processingEarlyContent) {
|
||||||
ApplicationDependencies.getEarlyMessageCache().store(id.getRecipientId(), id.getTimetamp(), content);
|
ApplicationDependencies.getEarlyMessageCache().store(selfId, id.getTimetamp(), content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue